Timeline Taxi Out now: my sci-fi novel Timeline Taxi is published!

Readonly classes in PHP 8.2

PHP 8.2 adds a new way of declaring classes: you can make them readonly. In practice, it means that all properties of that class will be readonly. This is especially useful when you're using data transfer objects or value objects, where a class only has public readonly properties.

In other words, instead of writing this:

class BlogData
{
    public function __construct(
        public readonly string $title,
        public readonly Status $status,
        public readonly ?DateTimeImmutable $publishedAt = null,
    ) {}
}

You can now write this:

readonly class BlogData
{
    public function __construct(
        public string $title,
        public Status $status,
        public ?DateTimeImmutable $publishedAt = null,
    ) {}
}

I've written about readonly properties before, so let's quickly summarise first:

Since readonly classes are merely syntactic sugar for making all properties of that class readonly, it means that the same rules apply to readonly classes as well.

# Write once

All properties of a readonly class can only be written once, and can not be unset:

readonly class BlogData { /* … */ }

$blogData = new BlogData(/* … */);

$blogData->title = 'other';

unset($blogData->title);

# Only typed properties

A readonly class can only have typed properties:

readonly class BlogData
{
    public string $title;
    
    public $mixed;
}

# No static properties

Since readonly properties cannot be static, readonly classes cannot have any static properties:

readonly class BlogData
{
    public static string $title;
}

# No default values

Properties of a readonly class can not have a default value unless you're using promoted properties:

readonly class BlogData
{
    public string $title = 'default';
}
readonly class BlogData
{
    public function __construct(
        public string $title = 'default', // This works
   ) {}
}

# No changes during inheritance

You cannot change the readonly class flag during inheritance:

readonly class BlogData { /* … */ }

class NewsItemData extends BlogData { /* … */ }

# No dynamic properties

Readonly classes also don't allow dynamic properties. This won't have a big impact since dynamic properties are deprecated in PHP 8.2 anyway, but means that you cannot add the #[AllowDynamicProperties] attribute to readonly classes:

#[AllowDynamicProperties]
readonly class BlogData { /* … */ }

# Reflection

Finally, there's a new reflection method to determine whether a class is readonly: ReflectionClass::isReadOnly(). You can also use ReflectionClass::getModifiers(), which will include the ReflectionClass::IS_READONLY flag.