Recommended Free Tools
PHP’s readonly feature can prevent a property from being reassigned, but it does not make an object deeply immutable or turn it into a sound domain-driven design (DDD) aggregate. Use it to protect values that should not change; use domain behavior and a carefully chosen aggregate boundary to protect business invariants.
What does readonly mean in PHP?
A PHP readonly property can be initialized once. After initialization, assigning to it again fails—even if the new value is identical. The property must have a declared type, cannot have an explicit default value, and must be initialized directly rather than through a reference. Readonly arrays cannot be changed by editing an offset or indirectly modifying the property.
As an Amazon Associate I earn from qualifying purchases.
Here is a small value type that uses readonly properties:
Free tools Windows power users keep installed
One-click scans. No signup required.
<?php
final class Money
{
public function __construct(
public readonly int $minorUnits,
public readonly string $currency,
) {}
public function equals(self $other): bool
{
return $this->minorUnits === $other->minorUnits
&& $this->currency === $other->currency;
}
}
The example uses integer minor units—such as cents—to avoid representing a money amount with a binary floating-point number. The equals() method makes the intended comparison explicit: for this type, both the amount and currency define the value.
#1 Best Overall
Readonly behavior has changed across PHP versions:
| PHP version | Relevant behavior |
|---|---|
| 8.1 | Readonly properties were introduced. They can be initialized once and cannot be reassigned afterward. |
| 8.2 | Readonly classes were introduced. All instance properties are readonly, and dynamic properties are disallowed. |
| 8.3 | A __clone() method may reinitialize readonly properties on the cloned object. |
| 8.4 | A readonly property’s default set visibility became protected(set), rather than the earlier private(set). Explicit visibility choices still matter. |
A readonly class must use typed instance properties, cannot declare static properties, and can only extend a readonly parent. A non-readonly child cannot extend a readonly class. These are language rules, not guarantees about the correctness of a domain model.
Are PHP readonly objects immutable?
Not necessarily. Readonly is shallow: it prevents replacing the value held by a property, but it does not freeze an object referenced by that property. The reference is fixed; the referenced object’s internals may not be.
<?php
final readonly class Event
{
public function __construct(public DateTime $createdAt) {}
}
$event = new Event(new DateTime('2026-10-09'));
$event->createdAt->modify('+1 day'); // The DateTime object can still change.
The property still refers to the same DateTime instance, so the assignment restriction is not broken. But the date inside that instance has changed. If a value object needs stable nested state, use immutable nested objects or otherwise control how they can be changed. For example, PHP’s DateTimeImmutable returns a new date object when modified instead of changing its existing instance.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Rank #2
Readonly therefore helps prevent certain state changes; it is not deep or transitive immutability. That distinction matters whenever callers can share references to objects held inside a readonly object.
What is the difference between a value object and an entity?
The key distinction is how the domain recognizes the object. A value object is defined by its attributes: two instances with the same relevant values can be interchangeable. An entity is recognized by its identity and lifecycle, even as its attributes change. Martin Fowler uses points with matching x and y coordinates as a value-object example, in contrast to objects identified by a unique identifier.
| Question | Value object | Entity |
|---|---|---|
| What makes it the same thing? | The relevant attribute values | A meaningful identity, such as an order number |
| What does a change mean? | Usually a different value; create a replacement | A state transition in the same object’s lifecycle |
| How should equality work? | Compare the domain-defining values | Compare identity, not every current attribute |
| Should separate references share a changing instance? | Usually not; immutable values help prevent aliasing surprises | Possibly, if the model’s lifecycle and consistency rules call for it |
A useful test is: if two instances contain the same domain value, should the business treat them as interchangeable? If yes, value semantics may fit. If identity, history, or lifecycle matters, entity semantics may be more appropriate.
Examples of candidate value objects include money, points, ranges, and a validated telephone number. A domain type can make intent clearer and prevent irrelevant operations, but that does not mean every primitive must become a class. Nor does immutability alone make an object a value object: a sales order may be unchanged during a read operation and still be an entity because its order number and lifecycle define it.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Fowler recommends immutable value objects in part because immutability avoids aliasing bugs: one caller changing a shared object can unexpectedly affect another. In PHP, readonly can help implement that choice, as long as nested state is also safe and equality is defined in terms of the domain’s values.
Should DDD value objects be readonly?
Often, but not because DDD imposes a language-level rule. Readonly is a good fit when changing a value after construction would be a domain error. A changed amount, currency, date range, or validated identifier can then be represented by constructing a new value object rather than mutating the old one.
Rank #4
Choose value semantics first, then use readonly where it supports those semantics. Decide which fields define equality, validate the value when constructing it, and make sure any objects nested inside it cannot undermine the intended stability. PHP does not automatically give ordinary objects value-based equality; the example’s equals() method illustrates one explicit approach.
Do not choose readonly merely to make a class appear safer. If the object’s identity persists while its state changes, it may be an entity instead. Its design should explain which changes are allowed and how those changes preserve the rules of the domain.
Can an aggregate root be readonly?
It can be readonly when it represents a snapshot or read-only representation. But readonly syntax does not create an aggregate boundary or enforce the invariants that make a live aggregate coherent.
In DDD, an aggregate root is the controlled entry point for operations that must preserve rules across the entities and values in an aggregate. Microsoft Learn’s DDD-oriented guidance emphasizes the root’s role in performing rules and invariants for that group. The root’s job is about which operations are allowed and what they guarantee—not simply whether properties can be reassigned.
A live aggregate can be mutable and still be well designed when its state changes go through behavior that preserves its invariants. Conversely, an aggregate whose properties are readonly does not automatically have invariant-preserving operations. An aggregate can also contain immutable value objects while its root remains behaviorally mutable.
For example, an order might expose an operation to add a line only when the order is open, and ensure that the resulting total is consistent with its lines. Making the order’s properties readonly would not supply that business rule. The important design decision is that callers use the root’s operation rather than changing aggregate members in ways that bypass the rule.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsHow should you choose between readonly values and mutable aggregates?
Start with the domain’s meaning and consistency boundary, not a blanket rule that every domain object should be readonly. Use these questions to guide the design:
- Identity: Is the object recognized by its values, or by a stable identity and lifecycle?
- Equality: Should equal attribute values make two instances interchangeable, or does identity decide whether they are the same?
- Change: Does a change create a new value, or is it a transition in an entity’s ongoing lifecycle?
- Invariants: Do rules span multiple members that need one controlled entry point for change?
- Nested state: Can an object stored in a readonly property still be mutated through another reference?
- Representation: Is this a snapshot or read model, or the live consistency boundary for business operations?
- Runtime and persistence: Which PHP version is deployed, and does the persistence approach support the chosen construction and hydration model? Check the documentation for the specific ORM and version before relying on compatibility; readonly syntax alone does not establish it.
Use readonly when it reinforces an intentionally stable value. Use an aggregate root when coordinated behavior must protect business rules. A well-chosen domain model may need both: immutable value objects inside an aggregate whose root controls meaningful state transitions.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




