readonly locks on first write, not at construction

A readonly property is not initialised-at-declaration; it is write-once from inside the declaring class, and the difference decides what a wither method can do.

final class Money
{
    public function __construct(
        public readonly int $cents,
        public readonly string $currency,
    ) {}
}

$m = new Money(4900, 'GBP');
$m->cents = 5000;
// Error: Cannot modify readonly property Money::$cents

// and from inside, after it has been set once:
//   the same error. once is once.

The property has no default and cannot be given one — a readonly property with an initialiser is a compile error, because a value assigned at declaration could never be changed and the language treats that as a constant instead. Initialising it outside the constructor is legal as long as it happens inside the declaring class, which is what lets a static factory do work before assigning. A subclass cannot initialise a parent’s readonly property, and that catches people.