Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

PHP’s readonly keyword can stop a property from being reassigned, but it does not make an object deeply immutable or turn it into a sound domain model. A value object is defined by value-based meaning and equality; a DDD aggregate root is defined by its role in controlling changes that must preserve business invariants. Those ideas can work together, but neither is a substitute for the other.

What does readonly mean in PHP?

PHP 8.1 introduced readonly properties. A typed readonly property can be initialized once; after that, assigning it again is an error—even if the new value is identical. It must be initialized directly, rather than through a reference, and it cannot have an explicit property default.

For a small value type, that can express an important rule: once a value has been constructed, its fields cannot be reassigned.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<?php
final class Money
{
    public function __construct(
        public readonly int $amount,
        public readonly string $currency,
    ) {}
}

This example prevents reassignment of $amount and $currency; it does not provide validation, arithmetic, or value-based equality automatically. Those behaviors still need to be designed. The PHP manual documents the language rules for readonly properties.

Version details that change the rules

  • PHP 8.1: readonly properties became available. Before PHP 8.4, their implicit set visibility was private to the declaring class.
  • PHP 8.2: readonly classes became available. A readonly class applies readonly to its instance properties and disallows dynamic properties.
  • PHP 8.3: a __clone() method may reinitialize readonly properties on the clone. This is a cloning-specific allowance, not permission to reassign the original object’s properties.
  • PHP 8.4: a readonly property’s default set visibility became protected(set), so a child class may set it, subject to the declared visibility rules.

A readonly class must use typed instance properties, cannot declare static properties, and can only extend a readonly parent. A non-readonly class cannot extend a readonly class. These constraints make readonly classes useful for types whose instance state should be fixed, but they are not a general-purpose immutability switch.

Are PHP readonly objects immutable?

Not necessarily. Readonly is shallow: it restricts writes to a property, not changes to the internals of an object held by that property. The reference is fixed; the referenced object’s internals may not be.

final class Settings
{
    public function __construct(public readonly MutableOptions $options) {}
}

$settings = new Settings(new MutableOptions());
$settings->options->enabled = false; // May be legal if MutableOptions allows it.
$settings->options = new MutableOptions(); // Not allowed.

The property cannot be replaced, but code with access to the referenced object may still mutate it. To make the overall value stable, use immutable nested objects or otherwise control their mutation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Readonly arrays have a different limitation: after initialization, their offsets and indirect modifications cannot be changed. Readonly also rejects indirect property modifications. A readonly property therefore prevents common mutation paths, but it does not guarantee that every object reachable from it is immutable.

What is the difference between a value object and an entity?

The key question is what makes two instances the same in the domain. Martin Fowler describes value objects as objects considered equal when their properties have equal values—for example, two points with the same coordinates. An entity is recognized by identity, often represented by an identifier, even if its other attributes change.

Question Value object Entity
What establishes sameness? The relevant attributes have the same values. A persistent identity or identifier remains the same.
What does a change mean? Usually a new value, represented by a new instance. A change in the lifecycle or state of the same domain object.
Should separate references share one changing instance? Usually not; interchangeable values reduce the need for shared identity. Possibly; different parts of the system may refer to the same identified object.

A useful test is: if two instances contain the same domain value, should the domain treat them as interchangeable? If so, value semantics may fit. If the object’s identity and lifecycle matter, entity semantics may fit. An order can remain the same order as its address or status changes; immutability during a particular read operation would not make it a value object.

Money, ranges, points, and telephone numbers can be modeled as value objects when their domain meaning supports it. A type such as TelephoneNumber can make intent clearer and centralize relevant validation, but not every primitive needs a wrapper class. The model should reflect the domain’s language, rather than a rule to turn every string into an object.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Readonly can help implement a value object by preventing reassignment and reducing aliasing bugs: one consumer cannot silently alter the value another consumer observes. Fowler recommends immutable value objects, with changes generally expressed by creating a new value. In PHP, that benefit depends on the nested state being immutable or controlled as well.

Should DDD value objects be readonly?

Often, yes: readonly properties or a readonly class are a natural fit when a value object should not change after construction. They help enforce that design at the language level. But readonly syntax does not determine whether the type has value semantics. That depends on how the domain defines equality and identity.

PHP objects are not automatically equal because their properties match. In particular, strict object comparison with === checks whether two variables refer to the same instance. If the domain needs two separate Money instances with the same amount and currency to count as equal, implement or otherwise define that comparison explicitly. Readonly protects state; it does not supply equality semantics.

Likewise, an immutable object can still be an entity. An order identified by an order number remains identity-based even if a particular representation of it cannot be changed. Immutability is a useful implementation property, not the definition of a value object.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Can an aggregate root be readonly?

It can be, depending on what the object represents. A readonly aggregate-shaped object may be appropriate as a snapshot or read representation. But readonly alone does not make it a useful live aggregate root when business operations need to change state.

In DDD, the aggregate root is the controlled entry point for operations that must preserve invariants across the aggregate. Microsoft Learn’s DDD-oriented guidance describes the root as the single entry point through which rules and invariants for a group of entities are performed. The boundary and operations—not a language keyword—are what provide that control.

Example: an order and its line items

Suppose an order must never exceed a quantity limit across its line items. A live aggregate root can expose an operation such as addItem(), check the limit, and update the order only when the invariant remains valid. Callers should not be able to bypass that rule by directly changing an item or editing a collection behind the root.

The order may contain immutable value objects such as money amounts or product descriptions while remaining behaviorally mutable as an entity. A successful business operation can change the order’s state without changing the order’s identity.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Design choice What it protects or represents What it does not provide by itself
Readonly value object Its established property values cannot be reassigned. Deep immutability, validation, or value-based equality.
Mutable aggregate root with controlled operations Business changes can be routed through behavior that preserves aggregate invariants. Automatic correctness if callers can bypass those operations or if the boundary is poorly chosen.
Readonly aggregate snapshot A fixed representation of aggregate state for a read-oriented use. A coherent way to perform a changing business lifecycle.

Not every application needs full DDD aggregates. Microsoft’s guidance emphasizes choosing boundaries around the domain and applying DDD patterns where business complexity warrants them; straightforward CRUD responsibilities may be handled with simpler designs.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

How should you choose?

  • Identity: Is the object recognized by its attributes, or does it have an identity and lifecycle independent of those attributes?
  • Equality: Should two separately created instances with matching values be interchangeable? If so, define that equality deliberately.
  • Change: Is a change a new value, or a state transition of the same entity?
  • Invariants: Do rules span multiple objects that must change consistently? If so, identify the boundary and ensure operations pass through the root.
  • Nested state: Does a readonly property hold a mutable object or collection that can still be changed through another reference?
  • PHP target: Does the deployed PHP version support the readonly behavior your design relies on, particularly clone reinitialization or PHP 8.4 set visibility?
  • Persistence: Check the documentation for the specific ORM and version before relying on readonly objects for hydration or persistence. PHP’s language rules alone do not establish ORM compatibility.

The practical distinction is simple: use readonly where reassignment would violate the object’s value semantics; define entities by identity and lifecycle; and define aggregate roots by the invariants their operations must protect. One model may use all three ideas without treating them as synonyms.

Further reading

  • Eric Evans, Domain-Driven Design: Tackling Complexity in the Heart of Software, for foundational domain-model concepts.
  • Vaughn Vernon, Implementing Domain-Driven Design, for deeper treatment of DDD modeling and value-object implementation.

These are DDD references, not PHP readonly manuals.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.