f in x
Readonly Properties and Readonly Classes in PHP 8: Practical Immutability for Safer Code
> cd .. / HUB_EDITORIALE
Sviluppo di siti web

Readonly Properties and Readonly Classes in PHP 8: Practical Immutability for Safer Code

[2026-06-10] Author: Ing. Calogero Bono

Have you ever spent hours chasing a bug because a property of an object was modified by a method that wasn't supposed to touch it? It happens to everyone. Especially when working with DTOs, domain models, or configurations that should remain fixed after initialization. PHP 8.1 introduced readonly properties, and PHP 8.2 took it further with readonly classes. These aren't just nice features: they change how you write code, reduce side effects, and make your objects predictable. At Meteora Web, we use them daily in Laravel projects and our proprietary platforms. This guide shows you how to use them for real, not just in theory.

Why Immutability Matters in PHP Too

PHP has historically been a dynamic and permissive language. You can modify a public property from anywhere in the code. That's convenient for quick scripting, but it's a disaster as a project grows. An object representing an order or a user shouldn't change after it's created, unless explicitly intended.

Immutability gives you guarantees: once a value is set, no method, function, or framework can accidentally alter it. The result: fewer bugs, easier testing, deterministic behavior. And you don't need a complex pattern—PHP gives you language-level tools.

Readonly Properties: How They Work (and What They Don't)

Readonly properties were introduced in PHP 8.1. The syntax is straightforward: prefix with readonly. The constraint is that the property can be initialized only once—typically in the constructor or directly in the declaration (if it's a non-static constant). After that, it cannot be modified.

Sponsored Protocol

class UserDTO {
    public readonly string $name;
    public readonly string $email;

    public function __construct(string $name, string $email) {
        $this->name = $name;
        $this->email = $email;
    }
}

$user = new UserDTO('Mario Rossi', 'mario@example.com');
echo $user->name; // Mario Rossi
$user->name = 'Luca'; // Error: Cannot modify readonly property

Key points to remember:

  • Readonly properties must have a type declaration. You can't write public readonly $name; without a type.
  • They cannot be static. Readonly is for instances.
  • The value can be a mutable object. Readonly prevents changing the reference, but it doesn't prevent modifying the object itself if it's mutable. Example: public readonly array $items; — can you do $user->items[] = 'new';? No, because that attempts to modify the readonly property (error). But if the value is an object like a Collection, you can call methods that modify it. So beware: readonly protects the reference, not the contents.

Common mistake: trying to initialize a readonly property in a method other than the constructor or outside the declaration. It won't work. The only times you can assign are in the declaration itself (with a constant value) or in the constructor.

Sponsored Protocol

Readonly Properties with Promoted Constructor

The most used combination: declare readonly properties directly in the constructor. PHP 8 allows you to merge declaration and assignment. It's clean and compact.

class ProductDTO {
    public function __construct(
        public readonly string $sku,
        public readonly float $price
    ) {}
}

With one line you have everything: constructor, property, type, immutability. Less boilerplate, more readability.

Readonly Classes in PHP 8.2: Immutability at the Class Level

PHP 8.2 introduced readonly classes. Add readonly before class and all properties automatically become readonly. Additionally, you cannot add non-readonly properties. Promoted or explicit constructors are fine.

readonly class OrderDTO {
    public function __construct(
        public string $orderId,
        public float $total
    ) {}
}

// Equivalent to making each property readonly
// You cannot declare normal properties in this class

Practical benefits:

Sponsored Protocol

  • Zero repetition: no need to write readonly on every property.
  • Clear intent: anyone reading sees immediately that the class is immutable.
  • The compiler (opcache) may optimize a bit, but it's usually negligible.

Be careful: A readonly class cannot extend a non-readonly class, and vice versa. Inheritance is only allowed between readonly classes. Also, properties in a readonly class cannot be static.

Practical Immutability: Real-World Examples from Our Work

We use readonly classes for DTOs, CQRS commands, domain events, and API responses. A concrete example: in an order management platform we built for a client, each order is represented by an OrderSnapshot class passed between services. Thanks to readonly, we are certain no service modifies the order data after creation. If a service needs to update the status, it returns a new instance. This makes the flow predictable.

readonly class OrderSnapshot {
    public function __construct(
        public int $id,
        public string $status,
        public float $total,
        public array $items
    ) {}

    // Factory method to create a new instance with a changed status
    public function withStatus(string $newStatus): self {
        return new self(
            id: $this->id,
            status: $newStatus,
            total: $this->total,
            items: $this->items
        );
    }
}

This way, the original state is never lost, and you can track changes by comparing instances.

Sponsored Protocol

Serialization and Readonly: What Changes

Readonly properties can be serialized normally with serialize() and json_encode(). However, when deserializing with unserialize() or ReflectionProperty::setValue(), PHP does not respect the readonly constraint—it can assign a value even to a readonly property. This is a known and intentional behavior to allow deserialization. If you need strict protection also during deserialization, handle it manually (e.g., make properties private and provide a factory method).

Reflection and Readonly: Limits to Know

With ReflectionProperty you can modify a readonly property by calling setValue(). It works, but it's considered an advanced use for ORM libraries or serializers. In normal application code, you should never rely on this. Readonly is a contract for the developer, not a security barrier.

When to Use Readonly and When Not

Use readonly for:

  • DTOs (Data Transfer Objects) that carry data between layers
  • Domain events
  • CQRS commands
  • Immutable configurations (e.g., connection parameters, system settings)
  • Value objects

Do not use readonly for:

Sponsored Protocol

  • Doctrine or Eloquent entities that need to be modified by the ORM (unless you use event sourcing or snapshots)
  • Objects that naturally change frequently (e.g., a user session, transient state)
  • Classes with injected service dependencies (not wrong, but often unnecessary if the service is mutable)

In Summary — What to Do Now

  1. Identify DTOs in your projects. Every class that only serves to transport data between a controller and a service, or between services, is a perfect candidate for readonly.
  2. Rewrite the first three DTO classes using readonly class or readonly properties. Use promoted constructors to reduce verbosity.
  3. Add a type exception if a readonly property is modified—PHP does it for you, but you can document why.
  4. Compare the code before and after. You'll notice the constructor becomes the only write point, and everything else is read-only. That reduces mental load.
  5. Update your code reviews: when you see a mutable public property in a class that looks like a DTO, ask if it can be readonly.

Immutability is not a trend: it's a discipline that pays off in the long run. PHP 8 gives you a clean syntax to implement it. At Meteora Web, we use it on all new projects and won't look back. Try it yourself.

Ing. Calogero Bono

> AUTHOR_EXTRACTED

Ing. Calogero Bono

Ingegnere Informatico, co-fondatore di Meteora Web. Esperto in architetture software, sicurezza informatica e sviluppo sistemi scalabili.
[ Read Full Dossier ]

> METEORA_WEB // DIGITAL AGENCY

We build the digital presence your business deserves.

Websites, social media, online advertising, e-commerce and high-performance hosting, engineered with method by computer engineers in Sciacca, for all of Italy.

> MW_JOURNAL

> READ_ALL()