Immutability in 7.4 is a convention enforced by discipline: a private typed property, a constructor that sets it, and no setter.
final class Money
{
private int $cents;
private string $currency;
public function __construct(int $cents, string $currency)
{
$this->cents = $cents;
$this->currency = $currency;
}
public function add(Money $other): self
{
return new self($this->cents + $other->cents, $this->currency);
}
}
final matters more than it looks: without it a subclass can add a setter and the guarantee is gone. Returning a new instance from every operation is what makes the object safe to pass around, and it is the half people skip when adding a “just this one” mutator. The readonly keyword arrives in 8.1 and enforces at the language level what this does by agreement — which is worth knowing because the migration then is mechanical.