PHP Question: Object Property Increment

25th April 2011

Question

What does the following snippet print and why?

  1. <?php
  2. class MyClass {
  3. public $value;
  4. }
  5.  
  6. function doSomething($object) {
  7. $object->value++;
  8. }
  9.  
  10. $a = new MyClass();
  11. $a->value = 1;
  12. doSomething($a);
  13.  
  14. print $a->value;









 

Answer

You might think that the result of the above code is 1, but the actual value produced is 2. This is because even though we are not specifically passing the function parameter by reference, we actually are because all objects in PHP are passed by reference.

This is a very important feature of PHP that will almost certainly lead to bugs if you don't keep it in mind when designing your systems. One thing that might trip you up is that PHP objects are passed by reference even when assigning variables. Take the following snippet, which following on from the last example.

  1. $b = $a;
  2. $b->value = 15;
  3. echo $a->value;

You would again expect that the output of this code is 2 (following on from the last example), but what we really get is 15. This happens because when you assign an object to another variable you are creating a reference to the original object. So when we try to change the value of an the 'value' property in $b we are actually changing the value in the original object.

In order to get around this and create a separate object we use the clone keyword. The clone keyword will create a shallow copy of the object which will not be attached to the original.

  1. $b = clone $a;
  2. $b->value = 15;
  3. echo $a->value;

The above example now prints out 2, as expected.

There is also a bit of a gotcha with regards to cloning objects. Notice that I said "shallow copy" when I introduced the term. This is because it will make a copy of the original object, but all inner objects are not cloned in the same way. Take the following code example (which doesn't follow on from any previous example).

  1. <?php
  2. class MyClass {
  3. public $value;
  4. }
  5.  
  6. class AnotherClass {
  7. public $something;
  8. }
  9.  
  10. $a = new MyClass();
  11. $a->value = new AnotherClass();
  12. $a->value->something = 5;
  13.  
  14. echo $a->value->something . "\n";
  15.  
  16. $b = clone $a;
  17.  
  18. $b->value->something = 10;
  19.  
  20. echo $b->value->something . "\n";
  21. echo $a->value->something . "\n";

The first echo call prints out 5, as you would expect, but the second two both print 10 because although we created a copy of the original object, but all inner objects are passed by reference. In order to allow inner objects to be copied in the same way a magic method called __clone() is available. This method is called whenever you clone an object. Rewriting the above example we can create an new AnotherClass() object so that when we change the value in object $a it is not also changed in object $b.

  1. <?php
  2. class MyClass {
  3. public $value;
  4.  
  5. public function __clone() {
  6. $this->value = new AnotherClass();
  7. }
  8. }
  9.  
  10. class AnotherClass {
  11. public $something;
  12. }
  13.  
  14. $a = new MyClass();
  15. $a->value = new AnotherClass();
  16. $a->value->something = 5;
  17.  
  18. $b = clone $a;
  19.  
  20. $b->value->something = 10;
  21.  
  22. echo $b->value->something;
  23. echo $a->value->something;

The above example now prints 10 followed by 5. The original value of the value held by the inner object in $a is not changed when the same value in $b is changed.

Of course, creating another object during the __clone() call means that we lose all of the original information from the object. We could manually transfer the values over, but this is an unmaintainable approach. The other way of doing this is to clone the inner object inside the __clone() method in the following way.

  1. class MyClass {
  2. public $value;
  3.  
  4. public function __clone() {
  5. $this->value = clone $this->value;
  6. }
  7. }

This creates a copy of the inner object from the original object to the current one.

Comments

Permalink

It prints 1, because altough the doSomething() method is called, the returned value isn't stored anywhere. So the value of $a->value is the one that was asigned earlyer.

Submitted by Mihai Baboi on Mon, 04/25/2011 - 13:59

Add new comment

The content of this field is kept private and will not be shown publicly.