PHP Classes and Objects
Cloning Objects
Problem
You want to copy an object.Solution
Copy objects by reference using =:$rasmus = $zeev;
Copy objects by value using clone:
$rasmus = clone $zeev;
Discussion
PHP copies objects by reference instead of value. When you assign an existing object to a new variable, that new variable is just another name for the existing object. Accessing the object by the old or new name produces the same results.To create an independent instance of a value with the same contents, otherwise known as copying by value, use the clone keyword. Otherwise, the second object is simply a reference to the first.
This cloning process copies every property in the first object to the second. This includes properties holding objects, so the cloned object may end up sharing object references with the original.
This is frequently not the desired behavior. For example, consider the aggregated version of Person that holds an Address object:
class Address {
protected $city;
protected $country;
public function setCity($city) { $this->city = $city; }
public function getCity() { return $this->city; }
public function setCountry($country) { $this->country = $country; }
public function getCountry() { return $this-> country;}
}
class Person {
protected $name;
protected $address;
public function __construct() { $this->address = new Address; }
public function setName($name) { $this->name = $name; }
public function getName() { return $this->name; }
public function __call($method, $arguments) {
if (method_exists($this->address, $method)) {
return call_user_func_array( array($this->address, $method), $arguments);
}
}
}
An aggregated class is one that embeds another class inside in a way that makes it easy to access both the original and embedded classes. The key point to remember is that the $address property holds an Address object.
With this class, this example shows what happens when you clone an object:
$rasmus = new Person;
$rasmus->setName('Rasmus Lerdorf');
$rasmus->setCity('Sunnyvale');
$zeev = clone $rasmus;
$zeev->setName('Zeev Suraski');
$zeev->setCity('Tel Aviv');
print $rasmus->getName() . ' lives in ' . $rasmus->getCity() . '.';
print $zeev->getName() . ' lives in ' . $zeev->getCity() . '.';
Rasmus Lerdorf lives in Tel Aviv.
Zeev Suraski lives in Tel Aviv.
Interesting. Calling setName() worked correctly because the $name property is a string, so it’s copied by value. However, because $address is an object, it’s copied by reference, so getCity() doesn’t produce the correct results, and you end up relocating Rasmus to Tel Aviv.
This type of object cloning is known as a shallow clone or a shallow copy. In contrast, a deep clone occurs when all objects involved are cloned.
Control how PHP clones an object by implementing a __clone() method in your class. When this method exists, PHP allows __clone() to override its default behavior, as shown:
class Person {
// ... everything from before
public function __clone() {
$this->address = clone $this->address;
}
}
Inside of __clone(), you’re automatically presented with a shallow copy of the variable, stored in $this, the object that PHP provides when __clone() does not exist.
Because PHP has already copied all the properties, you only need to overwrite the ones you dislike. Here, $name is okay, but $address needs to be explicitly cloned.
Now the clone behaves correctly:
$rasmus = new Person;
$rasmus->setName('Rasmus Lerdorf');
$rasmus->setCity('Sunnyvale');
$zeev = clone $rasmus;
$zeev->setName('Zeev Suraski');
$zeev->setCity('Tel Aviv');
print $rasmus->getName() . ' lives in ' . $rasmus->getCity() . '.';
print $zeev->getName() . ' lives in ' . $zeev->getCity() . '.';
Rasmus Lerdorf lives in Sunnyvale.
Zeev Suraski lives in Tel Aviv.
Using the clone operator on objects stored in properties causes PHP to check whether any of those objects contain a __clone() method. If one exists, PHP calls it. This repeats for any objects that are nested even further.
This process correctly clones the entire object and demonstrates why it’s called a deep copy.
No comments:
Post a Comment