rfc:locked-classes

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
Last revisionBoth sides next revision
rfc:locked-classes [2019/03/10 14:34] – created imsoprfc:locked-classes [2019/03/11 11:03] – Fix formatting in reflection section nikic
Line 1: Line 1:
 ====== PHP RFC: Locked Classes ====== ====== PHP RFC: Locked Classes ======
-  * Version: 0.+  * Version: 1.0 
-  * Date: 2019-03-06+  * Date: 2019-03-10
   * Author: Rowan Collins [IMSoP], rowan.collins@gmail.com   * Author: Rowan Collins [IMSoP], rowan.collins@gmail.com
-  * Status: Draft+  * Status: Under Discussion
   * First Published at: http://wiki.php.net/rfc/locked-classes   * First Published at: http://wiki.php.net/rfc/locked-classes
  
Line 10: Line 10:
 Object properties in PHP are primarily defined in class definitions; however, they can also be added to or removed from individual instances at any time. This RFC proposes an opt-in method for a class definition to disable this behaviour for all instances of that class. Object properties in PHP are primarily defined in class definitions; however, they can also be added to or removed from individual instances at any time. This RFC proposes an opt-in method for a class definition to disable this behaviour for all instances of that class.
  
-While setting properties which were not declared in a class definition, or unsetting properties which were declared, can be a useful tool, like many of PHP's dynamic features, it also makes certain mistakes easier. For instance, if a class defines a property ''public $normalise=true;'', and a user writes ''$instance->normalize=false;'', a new property will be silently added. Static analysis can highlight this mistake, but the language itself issues no warning.+While setting properties which were not declared in a class definition, or unsetting properties which were declared, can be a useful tool, like many of PHP's dynamic features, it also makes certain mistakes easier. For instance, if a class defines a property ''public $normalise=true;'', and a user writes ''%%$instance->normalize=false;%%'', a new property will be silently added. Static analysis can highlight this mistake, but the language itself issues no warning.
  
 Changing this behaviour for all objects would be a significant change to the language, with the potential to break a large amount of existing code. However, code written with no intention of using this dynamic behaviour would benefit from a way to switch it off. Changing this behaviour for all objects would be a significant change to the language, with the potential to break a large amount of existing code. However, code written with no intention of using this dynamic behaviour would benefit from a way to switch it off.
 +
 +While this can be achieved through strategic use of the ''%%__set%%'', ''%%__get%%'', and ''%%__unset%%'' magic methods, this is long-winded, hard to optimise, and interferes with other uses of those methods.
  
 ===== Proposal ===== ===== Proposal =====
Line 22: Line 24:
   * Attempting to call ''unset()'' on any property of the instance will throw an error, and the instance will not be modified.   * Attempting to call ''unset()'' on any property of the instance will throw an error, and the instance will not be modified.
  
-This behaviour will interact with the existing magic methods as follows:+==== Interaction with Magic Methods ====
  
-  * If the class declares a magic ''_\_set'' method, this will still be called when attempting to set an undefined property instead of the error being thrown. +The existing "property overloading" magic methods (''%%__set%%'', ''%%__get%%'', and ''%%__unset%%'') may still be defined on locked classes, and will continue to be called in the same circumstances. Specifically: 
-  * If the class declares a magic ''_\_get'' method, this will still be called when attempting to read an undefined property instead of the error being thrown. + 
-  * If the class declares a magic ''_\_unset'' method, this will be called when attempting to unset //any property which is not declared in the class, or is declared private//. For properties which are declared and public, this magic method is not called; if the class is declared "locked", this case will therefore throw an error.+  * If the class declares a magic ''%%__set%%'' method, this will still be called when attempting to set an undefined property instead of the error being thrown. 
 +  * If the class declares a magic ''%%__get%%'' method, this will still be called when attempting to read an undefined property instead of the error being thrown. 
 +  * If the class declares a magic ''%%__unset%%'' method, this will be called when attempting to unset //any property which is not declared in the class, or is declared private//. For properties which are declared and public, this magic method is not called; if the class is declared "locked", this case will therefore throw an error. 
 + 
 +==== Interaction with Inheritance ==== 
 + 
 +The "locked" flag is **not** applied automatically to sub-classes. Any class may be marked "locked", regardless of whether it inherits from a locked class or not. 
 + 
 +The rationale is that a sub-class can add any number of public properties not listed in the parent definition, or add magic methods; it would therefore be inconsistent to ban it from allowing dynamic properties. 
 + 
 +If the author of the class wants to prevent this, the combination "final locked class" can be used to prohibit any child classes, whether locked or not. 
 + 
 +===== Example ===== 
 + 
 +For more examples, see the tests included in the draft implementation. 
 + 
 +<code php> 
 +locked class TestClass { 
 +    public $definedProp; 
 +
 + 
 +$t = new testClass(); 
 + 
 +// Defined properties work as normal 
 +$t->definedProp = "OK"; 
 +echo $t->definedProp; 
 +unset($t->definedProp); 
 + 
 +// Undefined properties may not be read or written 
 +echo $t->nonExistentProp; # ERROR: Cannot access undefined property $nonExistentProp on locked class TestClass 
 +$t->nonExistentProp = "Not OK"; # ERROR: Cannot write undefined property $nonExistentProp on locked class TestClass 
 + 
 +// Existing properties may not be unset 
 +unset($t->definedProp); # ERROR: Cannot unset property $definedProp of locked class TestClass 
 +</code>
  
 ===== Naming ===== ===== Naming =====
  
-Newer versions of ECMAScript / JavaScript have [[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal|similar functionality, under the name "sealed objects"]]. However, the term "sealed class" has an unrelated meaning in languages such as Kotlin and C#, to do with limiting the class's participation in inheritance.+Newer versions of ECMAScript / JavaScript have [[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal|similar functionality, under the name "sealed objects"]]. However, the term "sealed class" has an unrelated meaning in languages such as C# and Kotlin, to do with limiting the class's participation in inheritance.
  
 Since the proposed modifier applies to a class, not an instance, it would be confusing to use the keyword "sealed" in this sense, so the synonym "locked" has been chosen instead. Since the proposed modifier applies to a class, not an instance, it would be confusing to use the keyword "sealed" in this sense, so the synonym "locked" has been chosen instead.
 +
 +The name "strict" was also considered, but this could mean a variety of things, and might mislead users into thinking other types of "strictness" will apply to the class.
  
 ===== Backward Incompatible Changes ===== ===== Backward Incompatible Changes =====
-No internal classesor classes in unmodified codewill change behaviour.+The keyword "locked" will become "semi-reserved"on the same list as "final" and "abstract"
 + 
 +No existing code will change behaviour, since the modifier must be explicitly added.
  
 Since it is a new keyword, it will not be possible to "polyfill" this functionality, or declare a "locked class" in code which needs to be compatible with earlier PHP versions. Since it is a new keyword, it will not be possible to "polyfill" this functionality, or declare a "locked class" in code which needs to be compatible with earlier PHP versions.
Line 45: Line 85:
  
 ==== To Opcache ==== ==== To Opcache ====
 +
 To be determined: are there any optimisations which interact with the behaviours being changed? To be determined: are there any optimisations which interact with the behaviours being changed?
  
 ==== To Reflection ==== ==== To Reflection ====
  
-* ReflectionClass::IS_LOCKED constant +The following additions will be made to expose the new flag via reflection:
-* Changes to ReflectionClass::getModifiers() and Reflection::getModifierNames() +
-* ReflectionClass::isLocked() method+
  
-===== Unaffected PHP Functionality ===== +  * New constant ReflectionClass::IS_LOCKED to expose the bit flag used for locked classes 
-List existing areas/features of PHP that will not be changed by the RFC.+  * The return value of ReflectionClass::getModifiers() will have this bit set if the class being reflected is locked 
 +  * Reflection::getModifierNames() will include the string "locked" if this bit is set 
 +  * A new ReflectionClass::isLocked() method will allow directly checking if a class is locked
  
-This helps avoid any ambiguity, shows that you have thought deeply about the RFC's impact, and helps reduces mail list noise.+===== Unaffected PHP Functionality ===== 
 +Calling ''unset'' on a typed property will still succeed, and result in an "undefined property" on next access, unless the class is also marked ''locked''.
  
 ===== Future Scope ===== ===== Future Scope =====
-TODO+Classes defined by extensions could be marked "locked" if it was considered beneficial. This RFC does not propose any such changes.
  
 ===== Proposed Voting Choices ===== ===== Proposed Voting Choices =====
Line 65: Line 107:
  
 ===== Patches and Tests ===== ===== Patches and Tests =====
-https://github.com/php/php-src/compare/PHP-7.4...IMSoP:rfc-locked-classes+A pull request containing an initial implementation with basic tests is available on github: https://github.com/php/php-src/pull/3931
  
 ===== Implementation ===== ===== Implementation =====
rfc/locked-classes.txt · Last modified: 2019/06/04 18:56 by imsop