rfc:readonly_properties_v2

Differences

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

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
Next revisionBoth sides next revision
rfc:readonly_properties_v2 [2021/06/03 12:51] nikicrfc:readonly_properties_v2 [2021/07/01 10:16] nikic
Line 2: Line 2:
   * Date: 2021-06-02   * Date: 2021-06-02
   * Author: Nikita Popov <nikic@php.net>   * Author: Nikita Popov <nikic@php.net>
-  * Status: Draft+  * Status: Voting
   * Target Version: PHP 8.1   * Target Version: PHP 8.1
   * Implementation: https://github.com/php/php-src/pull/7089   * Implementation: https://github.com/php/php-src/pull/7089
Line 76: Line 76:
 <PHP> <PHP>
 class Test { class Test {
-    public readonly int $prop = 0;+    public function __construct( 
 +        public readonly int $= 0
 +        public readonly array $ary = [], 
 +    ) {}
 } }
  
 $test = new Test; $test = new Test;
-$test->prop += 1; +$test->+= 1; 
-$test->prop++; +$test->i++; 
-++$test->prop+++$test->i; 
-$ref =& $test->prop+$test->ary[] = 1; 
-$test->prop =& $ref; +$test->ary[0][] = 1
-byRef($test->prop);+$ref =& $test->i
 +$test->=& $ref; 
 +byRef($test->i);
 foreach ($test as &$prop); foreach ($test as &$prop);
 </PHP> </PHP>
Line 154: Line 159:
 ==== Inheritance ==== ==== Inheritance ====
  
-It is not allowed to override a read-write parent property with a readonly property in a child class:+It is not allowed to override a read-write property with a read-only property or vice versa. Both of the following are not legal:
  
 <PHP> <PHP>
Line 165: Line 170:
 } }
 </PHP> </PHP>
- 
-However, the converse is legal: 
  
 <PHP> <PHP>
Line 173: Line 176:
 } }
 class B extends A { class B extends A {
-    // Legal: readonly -> readwrite+    // Illegal: readonly -> readwrite
     public int $prop;     public int $prop;
 } }
 </PHP> </PHP>
 +
 +It is obvious that overriding a readwrite property with a readonly property needs to be forbidden, because that may render operations performed in the parent class invalid. However, this proposal views readonly not just as a lack of capabilities (which would be safe to increase in a child class), but as an intentional restriction. Lifting the restriction in the child class could break invariants in the parent class. As such, a readonly modifier may be neither added nor removed during inheritance.
  
 It is interesting to consider how property redeclaration interacts with the restriction that initialization can only occur in the declaring class: It is interesting to consider how property redeclaration interacts with the restriction that initialization can only occur in the declaring class:
Line 191: Line 196:
 Here, initialization of ''B::$prop'' would be permitted both from inside ''A'' and ''B'', as both classes declare the property. A possible alternative would be to allow initialization only from ''B'', though that would mean that a redeclaration in a child class could break usage in a parent class. Here, initialization of ''B::$prop'' would be permitted both from inside ''A'' and ''B'', as both classes declare the property. A possible alternative would be to allow initialization only from ''B'', though that would mean that a redeclaration in a child class could break usage in a parent class.
  
-When the same property is imported from two traits, the ''readonly'' modifiers must match:+When the same property is imported from two traits, the ''readonly'' modifiers must also match:
  
 <PHP> <PHP>
Line 205: Line 210:
 } }
 </PHP> </PHP>
- 
-One could argue that it should be possible to merge a readonly and a readwrite property into a readwrite property. However, other modifiers currently also require strict equality, for example it is not possible to merge a public and a protected property. If these rules should be relaxed, they should be relaxed consistently. 
  
 Types on readonly properties remain invariant. One could argue that types of readonly properties could be covariant instead: Types on readonly properties remain invariant. One could argue that types of readonly properties could be covariant instead:
Line 259: Line 262:
  
 A ''ReflectionProperty::isReadOnly()'' method is added, which reports whether a property is declared as read-only. ''ReflectionProperty::getModifiers()'' will also report a ''ReflectionProperty::IS_READONLY'' flag. A ''ReflectionProperty::isReadOnly()'' method is added, which reports whether a property is declared as read-only. ''ReflectionProperty::getModifiers()'' will also report a ''ReflectionProperty::IS_READONLY'' flag.
 +
 +''ReflectionProperty::setValue()'' can bypass the requirement that initialization occurs from the scope where the property has been declared. However, reflection cannot modify a readonly property that has already been initialized.
 +
 +Similarly, closure rebinding can be used to bypass the initialization scope requirement.
 +
 +==== Serialization ====
 +
 +Readonly properties have no impact on serialization. As ''%%__unserialize()%%'' (and the legacy ''Serializable::unserialize()'') method are invoked without a prior constructor call, readonly properties will be in an uninitialized state and can be set by the ''%%__unserialize()%%'' implementation.
 +
 +This also applies to userland serializers and hydrators. As long as the object is created using ''ReflectionClass::newInstanceWithoutConstructor()'' or some other constructor-bypass, it is always safe to initialize readonly properties.
  
 ===== Rationale ===== ===== Rationale =====
  
-The readonly property concept introduced in this proposal provides strong immutability guarantees, which apply both inside and outside the class. Once a property has been initialized, it cannot be changed under any circumstances.+The readonly property concept introduced in this proposal provides strong immutability guarantees, which apply both inside and outside the class. Once a property has been initialized, it cannot be changed under any circumstances. Reading a readonly property will always return the same value, no matter what code runs in between: 
 + 
 +<PHP> 
 +class Test { 
 +    public readonly string $prop; 
 +     
 +    public function method(Closure $fn) { 
 +        $prop = $this->prop; 
 +        $fn(); // Any code may run here. 
 +        $prop2 = $this->prop; 
 +        assert($prop === $prop2); // Always holds. 
 +    } 
 +
 +</PHP>
  
 These guarantees are //too// strong for certain use-cases. For example, some classes may wish to have properties that are publicly readable, but can only be written from within the class. This is a much weaker guarantee, as the value of a property can change during the lifetime of an object. //Both// variants can be useful depending on the situation, and the addition of readonly properties neither precludes nor discourages the addition of asymmetric property visibility. These guarantees are //too// strong for certain use-cases. For example, some classes may wish to have properties that are publicly readable, but can only be written from within the class. This is a much weaker guarantee, as the value of a property can change during the lifetime of an object. //Both// variants can be useful depending on the situation, and the addition of readonly properties neither precludes nor discourages the addition of asymmetric property visibility.
Line 316: Line 342:
 ===== Vote ===== ===== Vote =====
  
-Yes/No.+Voting started on 2021-07-01 and closes on 2021-07-15. 
 + 
 +<doodle title="Add readonly properties as proposed?" auth="nikic" voteType="single" closed="false"> 
 +   Yes 
 +   No 
 +</doodle>
  
rfc/readonly_properties_v2.txt · Last modified: 2021/07/20 15:37 by nikic