loading...
Cover image for Demystifying clone in PHP

Demystifying clone in PHP

jmau111 profile image Julien Maury Updated on ・3 min read

In PHP, copying objects can be tricky. Let's see why.

What is cloning in PHP? In short, it's the act of copying an object.
Cloning is everything but the following:

class MyClass {}
$a = new MyClass;
$b = $a;
print_r($a);
print_r($b);
Enter fullscreen mode Exit fullscreen mode

With the code above, you get:

MyClass Object
(
)
MyClass Object
(
)
Enter fullscreen mode Exit fullscreen mode

Wait, isn't that we mean by "copying"? Not really. Here we get the same identifier.

Cloning is something else.

A clone is not an assignment

In the last example, we use the same reference, which means that any modification in $b will also apply to $a. We don't want that.

You can verify this assertion with the following code:

class MyClass {
    public $myProperty;
}
$a = new MyClass;
$a->myProperty = "test";
$b = $a;// assignment
$b->myProperty = "test copy ref";
echo $a->myProperty). PHP_EOL;
echo $b->myProperty;
Enter fullscreen mode Exit fullscreen mode

You get "test copy ref" for both. Crap!

Fortunately, with the keyword clone, we can solve this problem:

class MyClass {
    public $myProperty;
}
$a = new MyClass;
$a->myProperty = "test";
$b = clone $a;
$b->myProperty = "test copy ref";
echo $a->myProperty). PHP_EOL;
echo $b->myProperty;
Enter fullscreen mode Exit fullscreen mode

This time, we get what we want, $a->myProperty remains intact, and we can play with$b->myProperty.

Copying objects vs. copying scalars

You might wonder why we need that clone processing with objects. PHP does not copy integers, strings, booleans, floats, or arrays the same way.

PHP copies variables with those types by value. Objects do not work that way. PHP handles them with unique identifiers. The variable $a and $b hold those identifiers in memory.

When you assign $b = $a, you get the same identifier that points to the same object.

Shallow copy vs. deep copy

A deep copy is an act of copying absolutely everything.

By default, PHP only clones the object's properties. It skips any reference to any other object. It's called a shallow copy.

This behavior is helpful because, by default, we do not want to replicate all properties, whether they are references or values. It's an infrequent use case, and it might have a high cost.

Additionally, we want to avoid the "circular effect", a.k.a infinite recursion, when A depends on B, which depends on C, which depends on A, etc.

The __clone method

Under the hood, the clone keyword calls the magic method __clone() of the copied object.

Let's do a quick example:

class MyClass {
    public static $counterClones = 1;
    public function __clone() {
        self::$counterClones++;
    }
}
$a = new MyClass;
$b = clone $a;
$c = clone $a;
echo $a::$counterClones;// displays "3"
Enter fullscreen mode Exit fullscreen mode

The clone method is not an override at all. The shallow copy always occurs. See it as a callback.

The method does not return anything (void), and it does not accept any parameter.

Notwithstanding, you can fine-tune it, for example, if you want to exclude some property or if you want to perform a deep copy. Your changes apply to the cloned object.

Classic error with Datetime

One of the most frequent errors with DateTime objects in PHP is to assign them. It leads to unexpected errors.

Do not do the following:

$dateStart = new \DateTime();
$dateEnd = $dateStart;
$dateEnd->modify('+1 day');
Enter fullscreen mode Exit fullscreen mode

Write this instead:

$dateStart = new \DateTime();
$dateEnd = clone $dateStart;
$dateEnd->modify('+1 day');
print_r($dateStart);
print_r($dateEnd);
Enter fullscreen mode Exit fullscreen mode

In the first example, you tell PHP that both $dateStart and $dateEnd are tomorrow. It is most likely not what you want. To prevent that, use clone.

About comparisons

Cloning means copying. When you clone an object, you create a new instance, and that's what we want.

Two different objects are not equal:

$date1 = new \DateTime();
$date2 = clone $date1;
var_dump($date1 == $date2);// returns false
var_dump($date1 === $date2);// returns false
Enter fullscreen mode Exit fullscreen mode

Please note that object comparisons can be tricky too. Read the documentation

About Singletons

The use of Singletons is justified in cases where we want to restrict the number of instances that we can create from a class to save resources.

You cannot have more than one instantiation, and it's not rare to see the following implementation:

class MySingleton {

    private static $instance = null;
    private function __construct() {}

    public static function getInstance()
    {
       if (self::$instance == null) 
       {
        self::$instance = new MySingleton();
       }
       return self::$instance;
    }

    final private function __clone() {}
}
Enter fullscreen mode Exit fullscreen mode

As you can see in the example above, the singleton defines the __clone() method as final private. This way, if you try the following:

$a = MySingleton::getInstance();
$b = clone $a;
Enter fullscreen mode Exit fullscreen mode

you will get a fatal error :

Fatal error: Uncaught Error: Call to private MySingleton::__clone() from context

Wrap up

I hope you understand now how to clone PHP objects and why it's useful.

Posted on by:

jmau111 profile

Julien Maury

@jmau111

Practise what you preach.

Discussion

pic
Editor guide