8.3 relaxed __clone and did nothing for the case everybody has, which is a wither on a readonly class.
final readonly class Filter
{
public function __construct(
public ?string $search = null,
public ?Status $status = null,
public int $page = 1,
public int $perPage = 20,
public string $sort = 'created_at',
public Direction $direction = Direction::Desc,
) {}
public function withPage(int $page): self
{
return $this->with(['page' => $page]);
}
/** @param array<string, mixed> $overrides */
private function with(array $overrides): self
{
return new self(...[...get_object_vars($this), ...$overrides]);
}
}
The spread of get_object_vars into named arguments contains the repetition and introduces a coupling between property names and array keys that no tool checks — acceptable because the array never leaves the class. It also breaks the moment a promoted parameter is renamed without the override key changing, which is the sort of thing a test with one wither per property catches and nothing else does.