PHP 8.1 added readonly properties, letting you enforce immutability at the language level — once set in the constructor, a readonly property can never be changed again, which eliminates a whole category of “who mutated this” bugs.
class Money {
public function __construct(
public readonly int $amountCents,
public readonly string $currency
) {}
public function add(Money $other): self {
if ($this->currency !== $other->currency) {
throw new InvalidArgumentException('Currency mismatch');
}
// Can't mutate $this->amountCents - must return a new instance
return new self($this->amountCents + $other->amountCents, $this->currency);
}
}
$price = new Money(1999, 'EUR');
// $price->amountCents = 500; // Fatal error: Cannot modify readonly property
$total = $price->add(new Money(500, 'EUR')); // returns a new Money instead
Immutable value objects like this are much easier to reason about in concurrent or long-lived contexts — you never have to wonder whether some distant piece of code changed the object out from under you.