TL;DR

This is a classic case of PHP SNAFU. I will explain how and why it happens, but unfortunately as far as I can tell there is no possible solution that is satisfactory.

A brittle solution exists if you can run code before PHP shallow clones the object (e.g. by writing your own cloning method), but not if the code runs afterwards, which is how __clone works. However, this solution can still fail for other reasons outside your control.

There is also another option that is safe which involves a well-known "cloning" trick, but it also has drawbacks: it only works on data that is serializable, and it doesn't allow you to keep any references inside that data around even if you want to.

At the end of the day if you want to keep your sanity you will have to move away from implementing the properties $this->varN as references.

The plight of the poor PHP developer

Normally you would have to deep clone everything that needs to be cloned inside __clone . Then you would also have to reassign any references that still point to the instances that were just deep cloned.

You would think these two steps should be enough, for example:

public function __construct() { $this->data = new stdClass; $this->data->var1 = 'a'; $this->data->var2 = 'b'; $this->data->var3 = 'c'; $this->assignReferences(); } public function __clone() { $this->data = clone $this->data; $this->assignReferences(); } private function assignReferences() { $this->var1 = &$this->data->var1; $this->var2 = &$this->data->var2; $this->var3 = &$this->data->var3; }

However, this does not work. How can that be?

Zend engine references

If you var_dump($this->data) before and after assignReferences() in the constructor you will see that assigning those references causes the contents of $this->data to become references themselves.

This is an artifact of how references are internally implemented in PHP and there is nothing you can do about it directly. What you can do is convert them back to normal values first by losing all other references to them, after which cloning as above would work.

In code:

public function __construct() { $this->data = new stdClass; $this->data->var1 = 'a'; $this->data->var2 = 'b'; $this->data->var3 = 'c'; $this->assignReferences(); } public function makeClone() { unset($this->var1); // turns $this->data->var1 into non-reference unset($this->var2); // turns $this->data->var2 into non-reference unset($this->var3); // turns $this->data->var3 into non-reference $clone = clone $this; // this code is the same $clone->data = clone $clone->data; // as what would go into $clone->assignReferences(); // __clone() normally $this->assignReferences(); // undo the unset()s return $clone; } private function assignReferences() { $this->var1 = &$this->data->var1; $this->var2 = &$this->data->var2; $this->var3 = &$this->data->var3; }

This appears to work, but it's immediately not very satisfactory because you have to know that the way to clone this object is $obj->makeClone() instead of clone $obj -- the natural approach will fail.

However, there is also a more insidious bug here waiting to bite you: to un-reference the values inside $this->data you have to lose all references to them in the program. The code above does so for the references in $this->varN , but what about references other code might have created?

Compare this:

$original = new my_class; $new = $original->makeClone(); $new->var3 = 'd'; echo $original->var3; // works, "c"

To this:

$original = new my_class; $oops = &$original->var3; // did you think this might be a problem? $new = $original->makeClone(); $new->var3 = 'd'; echo $original->var3; // doesn't work!

We are now back to square one. And worse, there is no way to prevent someone from doing this and messing up your program.

Kill the references with fire

There is a guaranteed way to make the references inside $this->data go away no matter what: serialization.

public function __construct() { $this->data = new stdClass; $this->data->var1 = 'a'; $this->data->var2 = 'b'; $this->data->var3 = 'c'; $this->assignReferences(); } public function __clone() { $this->data = unserialize(serialize($this->data)); // instead of clone $this->assignReferences(); }

This works with the values in question, but it also has drawbacks:

You cannot have any values (recursively) inside $this->data that are not serializable. It will indiscriminately kill all references inside $this->data -- even those you might want to preserve on purpose. It's less performant (a theoretical point, to be fair).

So what to do?

After the obligatory bashing of PHP, follow the classic doctor's advice: if it hurts when you do something, then don't do it.