PHP 8.2: readonly
Classes
Following the readonly
feature in PHP 8.1 for class properties, PHP 8.2 supports declaring an entire class as readonly
.
A class declared as readonly
automatically makes all properties readonly
. PHP 8.2 deprecates dynamic properties, but readonly
classes hard-fail dynamic properties with an Error
exception.
readonly
Class Syntax
readonly
classes are declared with the readonly
keyword before the class declaration:
readonly class MyValueObject {
public string $myValue;
}
Abstract classes final classes can also be declared readonly
. The order of the keywords does not make a difference.
abstract readonly class Foo {}
final readonly class Bar {}
It is possible to declare a readonly
class with no properties in them, which effectively prevents dynamic properties while allowing child classes to explicitly declare their readonly
properties.
As it is the case for all PHP keywords, the readonly
keyword is case insensitive.
- Enums cannot contain properties at all, and it is not allowed to declare enums as
readonly
. - Traits cannot be declared
readonly
. - Interfaces cannot be declared
readonly
.
Attempting to declare Enums, traits, and Interfaces as readonly
results in a Parse error:
readonly interface Baz {}
Parse error: syntax error, unexpected token "interface", expecting "abstract" or "final" or "readonly" or "class" in ... on line ...
readonly
Semantics
When a class is declared readonly
, the entirety of the class and its declared members are considered readonly
. This follows exactly the same semantics of readonly
properties. To summarize, a readonly
property:
- Only be initialized from within the class scope.
- Cannot be modified once initialized.
- Cannot be
unset
once initialized. - Must be a typed property.
When a class is declared readonly
, that class:
- Must only contain typed properties
- Must not use dynamic properties
- Must not use
#[AllowDynamicProperties]
attribute - Cannot opt-out of the read-only status
When a readonly
class is extended by a subclass, that class:
readonly
classes must only contain typed properties
All properties of a readonly
class must be typed properties. For properties that cannot be strictly typed, consider using the mixed
type. This rule is the same as that individual readonly
properties must be typed properties.
Attempting to declare a readonly
class property without a type causes a Fatal error:
readonly class Test {
public $test;
}
Fatal error: Readonly property Test::$test must have type in ... on line ...
readonly
classes must not use dynamic properties
PHP 8.2 deprecates dynamic properties unless the class declares __get()
/__set()
methods or attributed with the AllowDynamicProperties
attribute.
A readonly
class must not use dynamic properties. Attempting to set a dynamic property results in an Error
exception:
readonly class Test {
public string $test;
}
$t = new Test();
$t->test2 = 'Hello';
Error: Cannot create dynamic property Test::$test2 in ...:...
readonly
classes must not use #[AllowDynamicProperties]
attribute
AllowDynamicProperties
attribute, introduced in PHP 8.2 can be used to explicitly opt-in for dynamic properties.
Because dynamic properties are not allowed in readonly
classes, using AllowDynamicProperties
attribute is not allowed, and results in a Fatal error.
#[AllowDynamicProperties]
readonly class Test {}
Fatal error: Cannot apply #[AllowDynamicProperties] to readonly class Test in ... on line ...
readonly
in Subclasses
Declaring a class readonly
does not prevent another class from extending the readonly
class. However, the subclass must not break the semantics of the parent readonly
class.
readonly
subclass must also be declared readonly
The child class must also explicitly declare that the class is readonly
. Note that when a parent class is provided by a different library/framework, declaring the parent class as readonly
will be effectively a backwards-compatibility break.
readonly class Test{}
readonly class SubTest extends Test {}
Attempting to declare a subclass extending a readonly
parent class without the readonly
keyword results in a Fatal error:
readonly class Test{}
class SubTest extends Test {}
Fatal error: Non-readonly class SubTest cannot extend readonly class Test in ... on line ...
Opting-out of read-only status
Once a class is declared readonly
, it is not possible to opt-out. This applies to subclasses as well, because subclasses must also explicitly be declared readonly
.
Mutability
Note that readonly
classes do not provide complete immutability. While readonly
classes are ideal for value-objects that guarantee their data does not change, objects stored in a readonly
property can change. This is the exact same behavior as readonly
properties.
Reflection API Changes
The ReflectionClass
class provides a new ReflectionClass::isReadOnly()
method that returns a boolean value indicating whether the class being reflected is declared readonly
.
readonly class MyValueObject {
public string $myValue;
}
$reflection = new ReflectionClass('MyValueObject');
$reflection->isReadOnly(); // true
Further, there is a new constant ReflectionClass::IS_READONLY
, assigned 65536
.
ReflectionClass::getModifiers
method returns a bitmask that reveals existence of the readonly
flag:
final readonly class MyValueObject {
public string $myValue;
}
$reflection = new ReflectionClass('MyValueObject');
$modifiers = $reflection->getModifiers();
($modifiers & ReflectionClass::IS_READONLY) === ReflectionClass::IS_READONLY; // true
Backwards Compatibility Impact
The readonly
class syntax is new in PHP 8.2, and classes that are declared readonly
result in a Parse error in older PHP versions.
As a stop-gap for readonly
classes, consider declaring all properties as readonly
, which is supported since PHP 8.1.