01 · THE IDEAREAD → TRY → LEARN
Why copying objects needs care
Assigning an object to another variable does not copy it. Both variables hold a handle to the same object, so a change through one name is visible through the other. When you want an independent object, you use the clone keyword:
$copy = clone $object;
The new object starts with the same property values as the original. That sounds complete, but the copy is shallow. Scalar values such as strings and integers are copied. A property that holds another object is copied as a handle, so the original and the clone now point at the same nested object.
Seeing the shallow copy
In this example a User owns an Address. After cloning, we change the name and the city on the clone only.
class Address
{
public function __construct(public string $city) {}
}
class User
{
public function __construct(public string $name, public Address $address) {}
}
$a = new User('Ada', new Address('London'));
$b = clone $a;
$b->name = 'Grace';
$b->address->city = 'Paris';
echo json_encode([
'a' => [$a->name, $a->address->city],
'b' => [$b->name, $b->address->city],
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";{"a":["Ada","Paris"],"b":["Grace","Paris"]}
The names differ, because strings were copied. The city changed for both users, because there is only one Address object.
Customizing the copy with __clone()
After PHP creates the shallow copy, it calls __clone() on the new object if the class defines it. Inside the method, $this is the copy, so you can replace shared nested objects with clones of their own.
class Address
{
public function __construct(public string $city) {}
}
class User
{
public function __construct(public string $name, public Address $address) {}
public function __clone(): void
{
$this->address = clone $this->address;
}
}
$a = new User('Ada', new Address('London'));
$b = clone $a;
$b->name = 'Grace';
$b->address->city = 'Paris';
echo json_encode([
'a' => [$a->name, $a->address->city],
'b' => [$b->name, $b->address->city],
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";{"a":["Ada","London"],"b":["Grace","Paris"]}
Now each user owns a separate Address. The important design choice is that the class itself decides which parts are private to each copy. Callers just write clone $user.
You do not have to clone every property. Sometimes sharing is correct, for example a shared logger or a service object. Clone what the object owns, and leave alone what it merely uses.
Arrays of objects
An array property is copied by value, but if its elements are objects, those elements are still shared handles. You must clone each element yourself.
class Item
{
public function __construct(public int $qty) {}
}
class Cart
{
public array $items;
public function __construct(Item ...$items)
{
$this->items = $items;
}
public function __clone(): void
{
$this->items = array_map(fn(Item $item) => clone $item, $this->items);
}
}
$one = new Cart(new Item(1), new Item(2));
$two = clone $one;
$two->items[0]->qty = 9;
echo json_encode([
array_map(fn(Item $i) => $i->qty, $one->items),
array_map(fn(Item $i) => $i->qty, $two->items),
]), "\n";[[1,2],[9,2]]
Without the array_map() in __clone(), both carts would report 9 for the first item.
Things to remember
cloneis a shallow copy: nested objects are shared unless you replace them.__clone()runs on the new object after the property values have been copied.- Property references also stay references, as the manual notes.
- Deep copy logic belongs in
__clone(), close to the properties it protects. - The manual describes a function form of
clonethat accepts property overrides as of PHP 8.5.0. This lesson targets PHP 8.4, so it uses only theclonekeyword.
Exercise contract
Write renameCopy(string $name, string $city, string $newName, string $newCity): array. Inside the file, build a Profile whose Address holds $city, clone the profile, then change the clone's name and city. Return an array with two keys, original and copy, each containing name and city. The original must keep its first values. The starter has a shallow clone, so you need to add the missing __clone() logic.
Recap
Use clone for an independent top-level object, and use __clone() to make nested owned objects independent too. Test by changing the copy and then checking the original.
Functions · Arrays · Types
- Distinguish shallow copies from referenced nested objects
- Implement and test a deterministic PHP 8.4 function