This is an old revision of the document!
PHP RFC: Readonly and immutable properties
- Version: 0.3.2
- Date: 2020-06-20
- Author: André Rømcke andre.romcke+php@gmail.com
- Based on previous RFCs by: Andrea Faulds ajf@ajf.me, Michal Brzuchalski michal@brzuchalski.com, Silvio Marijic marijic.silvio@gmail.com
- Proposed PHP version: PHP 8.0
- Status: Draft
- First Published at: http://wiki.php.net/rfc/readonly_and_immutable_properties
- Discussion: https://externals.io/message/ coming
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:
- 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
These three proposals will be offered as separate votes.
Under this RFC, code like
class User { private int $id; private string $name; public function __construct(int $id, string $name) { $this->id = $id; $this->name = $name; } public function __get($property) { if (property_exists($this, $property)) { // We return value here as non public properties are "readonly" in this class return $this->$property; } throw new PropertyNotFoundException($property, static::class); } public function __set($property, $value) { if (property_exists($this, $property)) { // Here private/protected property is attempted accessed outside allowed scope, so we throw throw new PropertyReadOnlyException($property, static::class); } throw new PropertyNotFoundException($property, static::class); } public function __isset($property) { return property_exists($this, $property); } public function __unset($property) { $this->__set($property, null); } }
might be written as
class User { <<Readonly>> public int $id; <<Readonly>> public string $name; public function __construct(int $id, string $name) { $this->id = $id; $this->name = $name; } }
Main differences to previous proposals
Readonly
This RFC differs from Readonly properties (2014, withdrawn) by instead using the recently accepted Attribute language feature for annotating Readonly properties. As done in Rust, and in user land annotations in many PHP frameworks.
Immutability
This RFC differs fromImmutability (2018, stale) by instead using the recently accepted Attribute language feature for annotating Immutable properties. Aligning with Readonly proposal within this RFC.
This RFC does not align with the semantics of the recent Write once properties, which is targeting a different problem.
Property Accessors Syntax
This RFC does not try to solve as wider use case as the different iterations of Property Accessors Syntax.
It's of this author's opinion that property type hinting and accessors where the two main obstacles to previous attempts at adding readonly semantics to PHP properties back in 2012-2014. And while readonly is rather hard to do in a clean way in PHP today, most other use cases offered by Accessors can be accomplished by plain PHP methods. Finally, Accessors overcomplicates readonly use case, and does not solve the needs for immutable property semantics.
Thus the author of this RFC believes what is proposed here should be done before any proposal for Accessors is re-considered, as anything here can be made syntax sugar for Accessors, if it ever gets accepted.
Proposal
1. Language ability to set property visibility for write access
This proposal adds support for runtime-enforced write visibility for declared properties. The following example illustrates the basic syntax:
class User { // Property is readonly in protected and public scope public:private int $id; // Property is readonly in public scope public:protected string $name; public function __construct(int $id, string $name) { $this->id = $id; $this->name = $name; } }
References
Attempting to pass a property value outside of allowed scope as reference, is an error.
TODO: Cover how visibility is manipulated with reflection
1.1. Language ability to set immutable property visibility for write access
This proposal adds support for runtime-enforced immutable write visibility for declared properties. The following example illustrates the basic syntax:
class User { // Property is immutable, can only be written to in __construct in private scope public:private immutable string $id; // Property is immutable, can only be written to in __construct in protected scope public immutable string $email; public function __construct(int $id, string $email) { $this->id = $id; $this->email = $email; } }
TODO: There should perhaps be a 1.B proposal here so 1. and 1.1 is only exposed via reflection for 2. and 3. use, but kept out of the language
Immutable semantics
An immutable property may only be written to in construct(), and unset in destruct(). Unless otherwise specified in visibility, the write/unset access is available in protected scope.
2. Readonly attribute
This proposal adds support for runtime-enforced readonly write visibility for declared properties. The following example illustrates the basic syntax:
use PHP\Attribute\Property\Readonly; class User { <<Readonly>> public string $id; public string $email; public function __construct(int $id, string $email) { $this->id = $id; $this->email = $email; } }
Attribute can also be set on class level, implicit setting it on all fields unless they have their own Property attribute:
use PHP\Attribute\Property\Readonly; <<Readonly>> class User { public string $id; public string $email; public function __construct(int $id, string $email) { $this->id = $id; $this->email = $email; } }
TODO: Define semantic in detail, see Rust's
3. Immutable attribute
This proposal adds support for runtime-enforced immutable write visibility for declared properties. The following example illustrates the basic syntax:
use PHP\Attribute\Property\Immutable; class User { <<Immutable>> public string $id; public string $email; public function __construct(int $id, string $email) { $this->id = $id; $this->email = $email; } }
Attribute can also be set on class level, implicit setting it on all fields unless they have their own Property attribute:
use PHP\Attribute\Property\Immutable; <<Immutable>> class User { public string $id; public string $email; public function __construct(int $id, string $email) { $this->id = $id; $this->email = $email; } }
TODO: Define semantic in detail, see C#'s readonly fields
Backward Incompatible Changes
Code that expects to be able to make properties writeable via reflection will have to adapt for new code taking advantage of this.
While ReflectionProperty::setAccessible() will still work like before, checks using isProtected() or isPrivate() won't detect if class has other visibility for write (proposal #1), or take into account specific attributes affecting write (assuming proposal #1 is voted down and Readonly and Immutable becomes own attribute logic instead of merely syntax sugar for #1)
Proposed PHP Version(s)
Next PHP version, 8.0 suggested.
Impact on extensions
More future extension code, and possible SPL code, can be written in PHP instead. This is in-line with other features already accepted for PHP 8.0.
Performance
Performance tests will need to be done once there is a implementation of this. Then overhead on properties, as well as measuring benefit over using magic methods.
Vote
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.
Errata
If there are any edge-cases found during implementation, they will appear here.
Changelog
Significant changes to the RFC are noted here.
- 2020-06-20 Initial RFC draft.