Magic Methods in Php – __clone() method

Lets understand what does object cloning mean.
To clone an object means to create a duplicate of an object. With regular variables $a = $b means that a new variable $a gets created that contains the value of $b. This means that 2 variables get created.

With objects $object2 = $object1 does not mean that a new object i.e. $object2 gets created. When we execute $object2 = $object1, the reference of $object1 is assigned to $object2.
This means that $object1 and $object2 point to the same memory space.

Look at the diagram below.

Lets see at an example where only references are assigned to another object:
class Customers {
 private $name;
 
 public function setName($name) {
  $this->name = $name;
 }
 
 public function getName() {
  return $this->name;
 }
}
 
$c1 = new Customers();
$c1->setName("Hiren");
 
$c2 = $c1; //only reference or memory assigned to $c2
 
$c2->setName("Bhavin");
 
echo $c1->getName()."\n";
echo $c2->getName()."\n";
Output:
Bhavin
Bhavin
In the above example, $c2 has the reference of $c1; therefore, when you set a new name in the $c2 object – $c1 object changes as well. Therefore, when an object is assigned as a reference; changes made to one object are also reflected in the other.
Therefore, to create a new $object2 object we must clone an object to create a new one. To clone an PHP5 Object a special keyword i.e. clone is used. Example below:

        $object2 = clone $object1;

After the above line is executed $object2 with a new memory space is created with the data members having the same value as that of $object1. This is also referred to as shallow copy.
The above technique works with a class having data members that are of intrinsic type i.e. int, boolean, string, float, etc.. However, this technique will not work with a class that has a data member which is an object of another class. In such a scenario, the cloned object continues to share the reference of the data member object of the class that was cloned.
So, how do we resolve this issue? Doing a regular shallow copy won’t help us. To allow aggregated objects (i.e. data members that are objects of another class) to also get cloned properly we need to use the concept of ‘deep copy‘ as opposed to ‘shallow copy‘. To implement a ‘deep copy‘ you should implement the magic method __clone().
You could also provide the implementation of __clone() magic method even when you don’t have an aggregated object. You would want to do this for providing necessary clean up operations, conversions or validations.

Lets explore a simple example of cloning intrinsic data types:

class Customers {
 private $name;
 
 public function setName($name) {
  $this->name = $name;
 }
 
 public function getName() {
  return $this->name;
 }
 
 public function __clone() {
  $c = new Customers();
  $c->setName($this->name);
  return $c;
 }
 
}
 
$c1 = new Customers();
$c1->setName("Hiren");
 
$c2 = clone $c1; //new object $c2 created
 
$c2->setName("Bhavin");
 
echo $c1->getName()."\n";
echo $c2->getName()."\n";
Output:
Hiren
Bhavin
In the above example, observe the line where the statement $c2 = clone $c1 is executed. This is internally represented as $c2 = $c1.__clone(). However, you cannot explicitly call the __clone() method on an object as the __clone() is automatically called. Now that $c1 and $c2 are two individual objects, changes made to one object is not reflected in the other.
Cloning aggregate objects (i.e. data members that are objects of another class)
To clone a class having aggregated objects, you should perform ‘deep copy‘. Please refer to the example below:
class Order {
 private $order_id;
 private $customer;
 
 public function setOrderId($order_id) {
  $this->order_id = $order_id;
 }
 
 public function getOrderId() {
  return $this->order_id;
 }
 
 public function setCustomer(Customer $customer) {
  $this->customer = clone $customer;
 }
 
 public function getCustomer() {
  return $this->customer;
 }
 
 public function __clone() {
 
  $order = new Order();
  $order->setOrderId($this->order_id);
 
 
  //force a copy of the same object to itself, otherwise
  //it takes the same instance. Seems like a bug to me
  $this->customer = clone $this->customer;
  $order->setCustomer($this->customer);
  return $order;
 }
 
}
 
class Customer {
 private $name;
 
 public function setName($name) {
  $this->name = $name;
 }
 
 public function getName() {
  return $this->name;
 }
 
 public function __clone() {
  $c = new Customer();
  $c->setName($this->name);
  return $c;
 }
 
}
 
$c = new Customer();
$c->setName("Hiren");
 
$order1 = new Order();
$order1->setOrderId("OD0001");
$order1->setCustomer($c);
 
$order2 = clone $order1;
 
$order2->getCustomer()->setName("Bhavin");
 
var_dump($c);
var_dump($order1);
var_dump($order2);
Output:
object(Customer)#1 (1) {
["name:private"]=>
string(5) "Hiren"
}
object(Order)#2 (2) {
["order_id:private"]=>
string(6) "OD0001"
["customer:private"]=>
object(Customer)#3 (1) {
["name:private"]=>
string(5) "Hiren"
}
}
object(Order)#4 (2) {
["order_id:private"]=>
string(6) "OD0001"
["customer:private"]=>
object(Customer)#6 (1) {
["name:private"]=>
string(6) "Bhavin"
}
}
In the example above both $order1 and $order2 have their own set of customer objects, therefore changes made to one object is not reflected in another. This example implements the concepts of ‘deep copy‘.
A special note on $this->customer = clone $this->customer; For some reason it is necessary to do this for proper working of aggregated cloning.

1 comment:

Anonymous said...

Really nice post Hireng, didn't know much about shallow and deep copies before I read this post.

This is a nice page too explaining magic methods in general:

PHP Magic Methods