Demystifying clone in PHP
Tags: php, oop
Published: 2020-08-09 21:32:53


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:

```php
class MyClass {}
$a = new MyClass;
$b = $a;
print_r($a);
print_r($b);
```
With the code above, you get:

```
MyClass Object
(
)
MyClass Object
(
)
```

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:

```php
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;
```
You get  "test copy ref" for both. Crap! 

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

```php
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;
```
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:

```php
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"
```

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:

```php
$dateStart = new \DateTime();
$dateEnd = $dateStart;
$dateEnd->modify('+1 day');
```

Write this instead:

```php
$dateStart = new \DateTime();
$dateEnd = clone $dateStart;
$dateEnd->modify('+1 day');
print_r($dateStart);
print_r($dateEnd);
```

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:

```php
$date1 = new \DateTime();
$date2 = clone $date1;
var_dump($date1 == $date2);// returns false
var_dump($date1 === $date2);// returns false
```

Please note that object comparisons can be tricky too. [Read the documentation](https://www.php.net/manual/en/language.oop5.object-comparison.php)

## 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:

```php
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() {}
}
```
As you can see in the example above, the singleton defines the `__clone()` method as `final private`. This way, if you try the following:

```php
$a = MySingleton::getInstance();
$b = clone $a;
```

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.