clone $obj makes a shallow copy; __clone() runs on the copy so it can clone inner objects. PHP 8.5's clone($obj, ['prop' => $value]) copies, runs __clone(), then assigns the new values with the caller's visibility, giving readonly classes one-line "withers". == compares class and property values; === means the same instance.
<?php
final readonly class Money {
public function __construct(public int $cents, public string $cur = 'USD') {}
public function withCents(int $cents): static { return clone($this, ['cents' => $cents]); }
}
class Order {
public function __construct(public int $id, public Money $total, public DateTime $placed) {}
public function __clone(): void { $this->placed = clone $this->placed; } // go deep
}
$a = new Order(5001, new Money(4200), new DateTime('2026-09-23'));
$b = clone($a, ['id' => 5002]);
$b->placed->modify('+1 day');
$c = $b->total->withCents(3900);
echo "$a->id {$a->placed->format('j M')} | $b->id {$b->placed->format('j M')} | $c->cents ",
json_encode([$a->total === $b->total, $c == new Money(3900), $c === new Money(3900)]), "\n";
try { clone($c, ['cents' => 0]); } catch (Error $e) { echo $e->getMessage(), "\n"; }Output
5001 23 Sep | 5002 24 Sep | 3900 [true,true,false] Cannot modify protected(set) readonly property Money::$cents from global scope
The orders share the immutable Money; the mutable DateTime had to be cloned. Readonly means protected(set), so outside code cannot clone with it. On objects that reference each other, == throws "Nesting level too deep".