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);
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:
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:
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:
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:
$dateStart = new \DateTime();
$dateEnd = $dateStart;
$dateEnd->modify('+1 day');
Write this instead:
$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:
$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
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() {}
}
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;
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.
Top comments (1)
object cloning example
Some comments have been hidden by the post's author - find out more