PHP 8.1: Readonly Properties
PHP 8.1 brings support for read-only class properties. A class property declared read-only is only allowed to be initialized once, and further changes to the property is not allowed.
Read-only class properties are declared with the readonly
keyword* in a typed property.
class User {
public readonly int $uid;
public function __construct(int $uid) {
$this->uid = $uid;
}
}
$user = new User(42);
Read-only property values can only be set from within the class itself, either from the constructor or another method. Once set, no further modifications are allowed to that property, even from within the class.
class User {
public readonly int $uid;
public function __construct(int $uid) {
$this->uid = $uid;
}
}
$user = new User(42);
$user->uid = 56;
Error: Cannot modify readonly property User::$uid in ...:...
readonly
as a function name
From PHP 8.1 and later, readonly
is a reserved PHP keyword. However, it explicitly allows declaring functions with name readonly
to accommodate existing PHP applications that declare their own readonly
functions, such as WordPress.
Constructor readonly
properties
Constructor properties, introduced in PHP 8.0 also support the new readonly
flag in the constructor.
class User {
public function __construct(public readonly int $uid) {}
}
The declaration above is equivalent to:
class User {
public readonly int $uid;
public function __construct(int $uid) {
$this->uid = $uid;
}
}
Property initialization
A property declared readonly
can be initialized from anywhere inside the class. It makes more sense to initialize read-only properties from the constructor, but it is not required to do so.
class User {
public readonly int $uid;
public function fetch(int $uid) {
$this->uid = $uid;
}
}
$user = new User();
$user->fetch(42);
The snippet above is valid, because the readonly
property $uid
is only written once, from the fetch()
method. However, a second call to the fetch()
function throws an Error, because it involves overwriting the $uid
property.
$user = new User();
$user->fetch(42);
$user->fetch(16);
Error: Cannot modify readonly property User::$uid in ...:...
Initializing a readonly
property outside the scope of the class is not allowed:
class User {
public readonly int $uid;
}
$user = new User();
$user->uid = 9;
Error: Cannot initialize readonly property User::$uid from global scope in ...:...
Read-only enforcement
PHP actively refuses to modify a readonly
property. This includes directly setting a value, incrementing it, references, and array operations.
class User {
public readonly int $uid;
public function __construct(int $uid) {
$this->uid = $uid;
}
}
$user = new User(42);
None of the following non-exhausting list of changes are allowed:
$user->uid = 16;
$user->uid++;
$uid_ref =& $user->uid;
Unsetting a readonly
property
Further, unsetting a readonly
property that is already initialized is not allowed either:
unset($user->uid);
Error: Cannot unset readonly property User::$uid in ...:...
Limitations
readonly
properties can greatly reduce boilerplate code that are often used to limit write access to class properties, which can now be simplified with a public readonly
property. However, the readonly
flag may not be an ideal fit in certain use cases.
Only supported on Typed Properties
Read-only properties are only supported on typed property. This is because properties declared without a type are implicitly null
until a value is set, and is not compatible with the read-only restriction. Typed properties are "uninitialized" until a value is set.
Attempting to declare a readonly
property without a type results in an error:
class User {
public readonly $uid;
}
Fatal error: Readonly property User::$uid must have type in ... on line ...
Mutability
A readonly
property does not provide immutability for objects. For example, if a readonly
property holds an object, that object itself can change:
class User {
public string $username;
}
class Post {
public readonly User $author;
public function __construct(User $author) {
$this->author = $author;
}
}
$user = new User();
$user->username = 'Foo';
$post = new Post($user);
$user->username = 'Bar';
In the snippet above, Post
class stored a readonly
property author
. The $user
object itself can change, and the readonly
property does not prevent modifications to the object itself.
If the immutability is required, make sure to clone
the object prior to assigning:
class Post {
public readonly User $author;
public function __construct(User $author) {
- $this->author = $author;
+ $this->author = clone $author;
}
}
Backwards Compatibility Impact
readonly
is a reserved keyword in PHP 8.1. Any existing classes or other symbols that use the name readonly
(case insensitive) will result in a syntax error in PHP 8.1.
It is not possible to port the readonly
functionality to older PHP versions. However, the @readonly
/ @property-read
annotations is understood by static analyzers such as Psalm.
class User {
/** @readonly */
public string $username;
}