rfc:readonly_and_immutable_properties

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
Last revisionBoth sides next revision
rfc:readonly_and_immutable_properties [2020/06/20 18:57] – formating andreromrfc:readonly_and_immutable_properties [2020/06/21 12:47] – some further fixes andrerom
Line 11: Line 11:
 ===== Introduction ===== ===== Introduction =====
  
-With the introduction of typed properties in PHP 7.4, properties have become far more powerful. However it is currently not possible to specify disconnected write vs read visibility for properties without having to resort to magic methods (getters and setters). This requires unnecessary boilerplate, makes usage less ergonomic and hurts performance. 
  
-This RFC resolves this issue by proposing: +**This is a early draft, currently looking for feedback on direction on what would make most sense to proposeespecially if there is any point in even exploring using Attributes for the features covered here or not.**
-  - Language change to make it possibility to specify write visibility disconnected from read. Including defining immutable write visibility. +
-  - Readonly attribute for properties and classes (all properties), if #1 is accepted this is merely syntax sugar +
-  - Immutable attribute for properties and classes (all properties), if #1 is accepted this is merely syntax sugar+
  
 +With the introduction of typed properties in PHP 7.4, properties have become far more powerful. However it is currently not possible to specify disconnected write vs read visibility for properties without having to resort to magic methods (getters and setters), for immutable semantic it's even more cumbersome. This requires unnecessary boilerplate, makes usage less ergonomic and hurts performance.
  
-//These three proposals will be offered as separate votes.//+This RFC resolves this issue by proposing a few options: 
 +  - Language approach: 
 +    - Change to make it possibility to specify write visibility disconnected from read. 
 +    - readonly keyword for write access, on property and class (implicit all properties) 
 +    - immutable keyword for write access, on property and class (implicit all properties) 
 +  - Attribute approach: 
 +    - Readonly attribute for properties, if #1.1 is accepted this is merely syntax sugar that adds possibility to set readonly on classes (implicit all properties). 
 +    - Immutable attribute for properties, if #1.2 is accepted this is merely syntax sugar that adds possibility to set immutability on classes (implicit all properties). 
 + 
 + 
 +//One of the options here are intended to be ruled out during early discussions//
  
  
Line 28: Line 35:
     private int $id;     private int $id;
     private string $name;     private string $name;
-    +
     public function __construct(int $id, string $name) {     public function __construct(int $id, string $name) {
         $this->id = $id;         $this->id = $id;
         $this->name = $name;         $this->name = $name;
     }     }
-    +
     public function __get($property)     public function __get($property)
     {     {
Line 42: Line 49:
         throw new PropertyNotFoundException($property, static::class);         throw new PropertyNotFoundException($property, static::class);
     }     }
-    +
     public function __set($property, $value)     public function __set($property, $value)
     {     {
Line 51: Line 58:
         throw new PropertyNotFoundException($property, static::class);         throw new PropertyNotFoundException($property, static::class);
     }     }
-    +
     public function __isset($property)     public function __isset($property)
     {     {
         return property_exists($this, $property);         return property_exists($this, $property);
     }     }
- 
  
     public function __unset($property)     public function __unset($property)
Line 71: Line 77:
     <<Readonly>>     <<Readonly>>
     public int $id;     public int $id;
-    +
     <<Readonly>>     <<Readonly>>
     public string $name;     public string $name;
-    + 
 +    public function __construct(int $id, string $name) { 
 +        $this->id = $id; 
 +        $this->name = $name; 
 +    } 
 +
 +</code> 
 + 
 + 
 +or just 
 + 
 +<code php> 
 +<<Readonly>> 
 +class User { 
 +    public int $id; 
 +    public string $name; 
     public function __construct(int $id, string $name) {     public function __construct(int $id, string $name) {
         $this->id = $id;         $this->id = $id;
Line 89: Line 111:
 ==== Readonly ==== ==== Readonly ====
  
-This RFC differs from [[rfc:readonly_properties|Readonly properties]] (2014, withdrawnby instead using the recently accepted Attribute language feature for annotating Readonly properties. As done in Rustand in user land annotations in many PHP frameworks.+This RFC aligns with [[rfc:readonly_properties|Readonly properties]] (2014, Withdrawn), however also proposing a more granular way of defining readonly access rules.
  
  
Line 95: Line 117:
  
  
-This RFC differs from[[rfc:immutability|Immutability]] (2018, staleby instead using the recently accepted Attribute language feature for annotating Immutable properties. Aligning with Readonly proposal within this RFC.+This RFC aligns with [[rfc:immutability|Immutability]] (2018, Stale), however also proposing a more granular way of defining immutable access rules.
  
-This RFC does __not__ align with the semantics of the recent [[rfc:write_once_properties|Write once properties]], which is targeting a different problem.+This RFC does __not__ align with the semantics of the recent [[rfc:write_once_properties|Write once properties]] (2020, Declined), which is targeting a different problem.
  
  
Line 112: Line 134:
 ===== Proposal ===== ===== Proposal =====
  
-==== 1. Language ability to set property visibility for write access ====+==== Common semantics ====
  
-This proposal adds support for runtime-enforced write visibility for declared properties. The following example illustrates the basic syntax:+== References == 
 + 
 +Attempting to pass a property value outside of allowed writable scope as a reference, results in an error. 
 + 
 + 
 +==== 1. Language Approach ==== 
 + 
 + 
 +=== 1.1 Language ability to set property visibility separately for write access === 
 + 
 +This proposal adds support for enforced write visibility checks for declared properties. The following example illustrates the basic syntax:
  
 <code php> <code php>
Line 120: Line 152:
     // Property is readonly in protected and public scope     // Property is readonly in protected and public scope
     public:private int $id;     public:private int $id;
-    +
     // Property is readonly in public scope     // Property is readonly in public scope
     public:protected string $name;     public:protected string $name;
          
 +    // Property is writeonly in public and protected scope
 +    private:public string $newName;
 +
     public function __construct(int $id, string $name) {     public function __construct(int $id, string $name) {
         $this->id = $id;         $this->id = $id;
Line 131: Line 166:
 </code> </code>
  
 +The format is "<read_visibility>:<write_visibility>", and if you omit the last visibility value you will like before implicit set both read and write visibility at once _(unless other future keywords or attributes states otherwise).
  
  
-== References ==+== Reflection ==
  
-Attempting to pass a property value outside of allowed scope as referenceis an error.+When using reflection, methods such as "ReflectionProperty::setAccessible()" will work as beforeit will implicit set visibility for both read and write.
  
 +However with this proposal the following existing methods will represent read visibility for cases where it differs:
 +- ReflectionProperty::isPrivate
 +- ReflectionProperty::isProtected
 +- ReflectionProperty::isPublic
  
-//TODO: Cover how visibility is manipulated with reflection//+And for checking separate write visibility the following methods may be used: 
 +- ReflectionProperty::isWritePrivate — Checks if property is writable in private 
 +- ReflectionProperty::isWriteProtected — Checks if property is writable in protected 
 +- ReflectionProperty::isWritePublic — Checks if property is writable in public
  
  
-=== 1.1. Language ability to set immutable property visibility for write access ===+"Reflection::getModifiers()" and "Reflection::getModifierNames()" will need adaption too, and proposal is to adapt it so "getModifierNames()" continues to return the visibility as specified, meaning it may now return for instance "public:protected" as one of the strings returned.
  
-This proposal adds support for runtime-enforced immutable write visibility for declared properties. The following example illustrates the basic syntax:+//TODO: Expand this with modifier ints representing all variations and their names// 
 + 
 +=== 1.2 readonly keyword === 
 + 
 +This proposal adds support for runtime-enforced readonly write visibility for declared properties. The following example illustrates the basic syntax:
  
 <code php> <code php>
 class User { class User {
-    // Property is immutable, can only be written to in __construct in private scope +    // Property is readonlyand can only be written to in protected scope 
-    public:private immutable string $id;+    public readonly int $id;
          
-    // Property is immutable, can only be written to in __construct in protected scope +    // Property is readonlyand can only be written to in private scope 
-    public immutable string $email;+    protected readonly string $name; 
 + 
 +    // [assuming 1.1 is accepted] Invalid declaration (visibility is already stating property is readonly) 
 +    public:private readonly string $email; 
 + 
 +    public function __construct(int $id, string $name) { 
 +        $this->id = $id; 
 +        $this->name = $name; 
 +    } 
 +
 +</code> 
 + 
 +Keyword can also be set on class level, implicit setting it on all fields unless they have their own immutable attribute: 
 + 
 +<code php> 
 +readonly class User { 
 +    // Property is readonly, and can only be written to in protected scope 
 +    public int $id;
          
 +    // Property is readonly, and can only be written to in private scope
 +    protected string $name;
 +
 +    // [assuming 1.1 is accepted] Invalid declaration (visibility is already stating property is readonly)
 +    public:private string $email;
 +
 +    public function __construct(int $id, string $name) {
 +        $this->id = $id;
 +        $this->name = $name;
 +    }
 +}
 +</code>
 +
 +
 +== Readonly semantics ==
 +
 +An readonly property may only be written to in scope lower than what is define as its read+write visibility, so if visibility is public, it may only be written to in protected scope.
 +
 +
 +== Reflection ==
 +
 +When using reflection, methods such as "ReflectionProperty::setAccessible()" will work as before, it will implicit disable immutable flag.
 +
 +Furthermore the following method is proposed added to be able to detect immutable properties:
 +- ReflectionProperty::isImmutable
 +
 +
 +"Reflection::getModifiers()" and "Reflection::getModifierNames()" will need adaption too to add int and keywords for "immutable".
 +
 +//TODO: Expand this with specific modifier int for "immutable"//
 +
 +
 +=== 1.3 immutable keyword ===
 +
 +This proposal adds support for runtime-enforced immutable write visibility for declared properties. The following example illustrates the basic syntax:
 +
 +<code php>
 +class User {
 +    // Property is immutable, can only be written to in __construct in protected scope
 +    public immutable int $id;
 +
 +    // [assuming 1.1 is accepted] Property is immutable, can only be written to in __construct in private scope
 +    public:private immutable string $email;
 +
     public function __construct(int $id, string $email) {     public function __construct(int $id, string $email) {
         $this->id = $id;         $this->id = $id;
Line 160: Line 268:
 </code> </code>
  
 +Keyword can also be set on class level, implicit setting it on all fields unless they have their own readonly attribute:
  
-//TODO: There should perhaps be a 1.B proposal here so 1. and 1.1 is only exposed via reflection for 2. and 3. usebut kept out of the language  //+<code php> 
 +immutable class User { 
 +    // Property is immutable, can only be written to in during construction in protected scope 
 +    public int $id; 
 +     
 +    // Property is immutable, can only be written to during construction in private scope 
 +    protected string $email; 
 + 
 +    public function __construct(int $idstring $email) { 
 +        $this->id = $id; 
 +        $this->email = $email; 
 +    } 
 +
 +</code> 
  
  
 == Immutable semantics == == Immutable semantics ==
  
-An immutable property may only be written to in __construct(), and unset in __destruct()+An immutable property may only be written to in construct and in other methods involved in object creation (set_state, unserialize, and wakeup), besides that it is allowed to be unset in destruct
-Unless otherwise specified in visibilitythe write/unset access is available in protected scope.+
  
 +Unless otherwise specified in visibility, the write/unset access is available within protected scope.
  
  
-==== 2. Readonly attribute ====+== Reflection == 
 + 
 +When using reflection, methods such as "ReflectionProperty::setAccessible()" will work as before, it will implicit disable immutable flag. 
 + 
 +Furthermore the following method is proposed added to be able to detect immutable properties: 
 +- ReflectionProperty::isImmutable 
 + 
 + 
 +"Reflection::getModifiers()" and "Reflection::getModifierNames()" will need adaption too to add int and keywords for "immutable"
 + 
 +//TODO: Expand this with specific modifier int for "immutable"// 
 + 
 +==== 2. Attributes ==== 
 + 
 +With the recently accepted [[rfc:attributes_v2|Attribute v2 RFC]], another option here, or an supplemental one , would be to use attributes for introducing Readonly and Immutable semantics. Similar to how Rust does with its [[https://docs.rs/readonly/0.1.6/readonly/|readonly create]]. 
 + 
 +However the Attribute RFC does not allow for what is being drafted here, so this would need suggesting a way for userland classes to tell parser / compiler to enhance language features. 
 + 
 +**As such, maybe this should be completely omitted from the proposal?** Should we aim for concepts in 1.1, 1.2 and 3.0 in this RFC? 
 + 
 + 
 +=== 2.1 Readonly attribute ===
  
  
Line 177: Line 320:
  
 <code php> <code php>
-use PHP\Attribute\Property\Readonly;+use PHP\Attribute\PropertyVisibility\Readonly;
  
 class User { class User {
     <<Readonly>>     <<Readonly>>
-    public string $id;+    public int $id;
  
 +    // This property is not readonly
     public string $email;     public string $email;
-    +
     public function __construct(int $id, string $email) {     public function __construct(int $id, string $email) {
         $this->id = $id;         $this->id = $id;
Line 192: Line 336:
 </code> </code>
  
-Attribute can also be set on class level, implicit setting it on all fields unless they have their own Property attribute:+Attribute can also be set on class level, implicit setting it on all fields unless they have their own PropertyVisibility attribute:
  
 <code php> <code php>
-use PHP\Attribute\Property\Readonly;+use PHP\Attribute\PropertyVisibility\Readonly;
  
 <<Readonly>> <<Readonly>>
 class User { class User {
-    public string $id; +    // This property is readable in public scope and writeable in protected 
-    public string $email;+    public int $id;
          
 +    // This property is readable in protected scope and writeable in private
 +    protected string $email;
 +
     public function __construct(int $id, string $email) {     public function __construct(int $id, string $email) {
         $this->id = $id;         $this->id = $id;
Line 209: Line 356:
 </code>  </code> 
  
 +//For readonly semantics see proposal 1.2//
  
-//TODO: Define semantic in detail, see [[https://docs.rs/readonly/0.1.6/readonly/|Rust's]] // 
  
 +== Reflection ==
  
-==== 3. Immutable attribute ====+//TODO: show example on reading /setting attribute via reflection, and how this relates to "ReflectionProperty::setAccessible()".//
  
-This proposal adds support for runtime-enforced immutable write visibility for declared properties. The following example illustrates the basic syntax:+ 
 + 
 +=== 2.2. Immutable attribute === 
 + 
 +This proposal adds a compiler attribute which implies a runtime-enforced immutable write visibility checks for declared properties. The following example illustrates the basic syntax:
  
 <code php> <code php>
-use PHP\Attribute\Property\Immutable;+use PHP\Attribute\PropertyVisibility\Immutable;
  
 class User { class User {
     <<Immutable>>     <<Immutable>>
-    public string $id;+    public int $id;
  
     public string $email;     public string $email;
-    +
     public function __construct(int $id, string $email) {     public function __construct(int $id, string $email) {
         $this->id = $id;         $this->id = $id;
Line 235: Line 387:
  
  
-Attribute can also be set on class level, implicit setting it on all fields unless they have their own Property attribute:+Attribute can also be set on class level, implicit setting it on all fields unless they have their own PropertyVisibility attribute:
  
 <code php> <code php>
-use PHP\Attribute\Property\Immutable;+use PHP\Attribute\PropertyVisibility\Immutable;
  
 <<Immutable>> <<Immutable>>
 class User { class User {
-    public string $id;+    public int $id;
     public string $email;     public string $email;
-    +
     public function __construct(int $id, string $email) {     public function __construct(int $id, string $email) {
         $this->id = $id;         $this->id = $id;
Line 253: Line 405:
  
  
-//TODO: Define semantic in detail, see C#'s [[https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/classes#readonly-fields|readonly fields]]//+//For immutable semantics see proposal 1.3// 
 + 
 +== Reflection == 
 + 
 +//TODO: show example on reading /setting attribute via reflection, and how this relates to "ReflectionProperty::setAccessible()".// 
  
  
Line 279: Line 436:
 As this is a language change, a 2/3 majority is required. As this is a language change, a 2/3 majority is required.
 //RFC is in draft, and will undergo discussion phase before it is put for a vote.// //RFC is in draft, and will undergo discussion phase before it is put for a vote.//
 +
 +===== References =====
 +
 +
 +  * [[https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/classes#readonly-fields|C# readonly fields]], semantically similar to what is proposed as "immutable" here.
  
  
Line 291: Line 453:
 Significant changes to the RFC are noted here. Significant changes to the RFC are noted here.
  
-  * 2020-06-20 Initial RFC draft.+  * 2020-06-21 Initial early draft to get feedback on direction
  
  
rfc/readonly_and_immutable_properties.txt · Last modified: 2020/06/21 18:47 by andrerom