Lesson 82 of 100
Classes15 min5 checksPHP 8.5

Clone Objects Deliberately

Learn how PHP's clone keyword makes a shallow copy, why nested objects stay shared, and how __clone() lets you build a deliberate deep copy.

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.

Example
<?php
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";
Output
{"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.

Example
<?php
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";
Output
{"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.

Example
<?php
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";
Output
[[1,2],[9,2]]

Without the array_map() in __clone(), both carts would report 9 for the first item.

Things to remember

  • clone is 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 clone that accepts property overrides as of PHP 8.5.0. This lesson targets PHP 8.4, so it uses only the clone keyword.

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.

Before you start

Functions · Arrays · Types

You’ll practice
  • Distinguish shallow copies from referenced nested objects
  • Implement and test a deterministic PHP 8.4 function
Your task

Make renameCopy() pass all 5 checks. Run your code as often as you like, then check your answer.

exercise.phpSaved in this browserPHP 8.5
CONSOLE & CHECKS● Ready

Ready when you are

Your output will appear here.
Make it work. Then make it pass.

PHP loads on your first run. Each check starts fresh. Your code stays in this browser; clearing site data removes your progress. A check or the task looks wrong? Report a problem with this exercise.