Eight single-property wrappers over a string, none of which validated anything.
// what it was
final readonly class SupplierReference
{
public function __construct(public string $value) {}
}
// what it enforced: nothing. any string is valid.
// what it cost: 8 classes, 8 normalizers, 8 casts,
// and a conversion at every boundary.
// what earns the pattern:
final readonly class EmailAddress
{
public function __construct(public string $value)
{
if (! filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new InvalidEmailAddress($value);
}
}
}
A value object with no invariant is a type alias that PHP does not have, and the type safety it provides — you cannot pass a supplier reference where a customer reference is expected — is real and is not worth four files. The eight that were deleted had never prevented a bug and the ones with invariants had prevented several.