Writing readonly on every property of a value object was the only way to say something the class as a whole was trying to say.
// 8.1
final class Money
{
public function __construct(
public readonly int $cents,
public readonly string $currency,
) {}
}
// 8.2
final readonly class Money
{
public function __construct(
public int $cents,
public string $currency,
) {}
}
The class modifier applies to every declared property and to every property declared by a child class, which is the part worth noticing — it is inherited downwards rather than being a shorthand applied at declaration. Static properties are excluded entirely, so a readonly class may still have mutable static state, which is a gap somebody will find. It composes with final and the two mean different things: final stops extension and readonly constrains it.