A readonly value object that wanted a lazily computed cache, which is exactly what readonly forbids and is usually a signal.
final readonly class Report
{
// this cannot exist
private ?array $computed;
}
// the three options:
// 1 drop readonly. loses the guarantee for one field.
// 2 a separate memoising wrapper. one more class.
// 3 compute in the constructor. eager, and here it
// is 40ms that is always needed.
// we took (3).
The wrapper is the textbook answer and adds a class whose only purpose is to hold a cache, which is worth it when the computation is expensive and conditional. Ours was neither — every caller used the value — so the laziness was a habit rather than a requirement, and the constructor was the honest place.