PHP 8.2: readonly Classes

Version8.2
TypeNew Feature

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:

When a class is declared readonly, that class:

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.


RFC Discussion Implementation