0

In some third party code, that I am not allowed to change, something peculiar is happening.

They write a variable (an array containing objects) to the session (without serialising it) and then iterate using a foreach on the original variable (without using references). Whenever they change a value, the corresponding value in the session is also changed. I was able to create a smaller example that has the same behaviour:

$test = array((object)array("categories" => "test"));

$_SESSION['woot'] = $test;

print_r($_SESSION['woot']);

foreach ($test as $a) {

    if (!is_array($a->categories)) $a->categories = array(); 

}

print_r($_SESSION['woot']);

This is the result:

Array
(
    [0] => stdClass Object
        (
            [categories] => test
        )

)
Array
(
    [0] => stdClass Object
        (
            [categories] => Array
                (
                )

        )

)

I already noticed that, when I serialize and unserialize the object array, the problem does not occur.

Does anyone have an idea about what's happening here? Is it the code? Is it an incorrect server setting? I'd like to know a little more before I contact the devs of the code.

Additional information:

  • I am using PHP Version 5.3.14
  • Register globals is switched off

Regards, Joost.

jberculo
  • 1,084
  • 9
  • 27

2 Answers2

3

A variable holding an object only holds a reference to that object. Assigning an object (an object reference) from one variable to another does not make a copy of the object, there's still only one object. If you modify that object, all variables holding a reference to that object will see the change, because there's only one object instance.

If you want to make a copy of an object, you need to explicitly clone it.

deceze
  • 510,633
  • 85
  • 743
  • 889
1

In PHP objects are passed by reference and PHP uses copy-on-write.

$test = array((object)array("categories" => "test"));
// it is still the same array
$_SESSION['woot'] = $test;
foreach ($test as $a) {
    // You change something in an object, which is passed by reference
    if (!is_array($a->categories)) $a->categories = array();
}

Serialization and deserialization creates new objects

$test = array((object)array("categories" => "test"));
// new array with new objects
$test = unserialize(serialize($test));
Michal Brašna
  • 2,293
  • 13
  • 17
  • 1
    I always thought a foreach only used references when you specificly said it should by adding an &. foreach($a => &$b) Learned something again. – jberculo Feb 19 '14 at 14:42
  • @jberculo It is true for all variable types except for objects, where `&$a` is default behavior and is same as `$a`. – Michal Brašna Feb 19 '14 at 14:43
  • There is one thing, however. The third party claims that they can't reproduce this problem with the same codebase. Is there a PHP version component at work as well? – jberculo Feb 19 '14 at 14:56
  • @jberculo This behavior is core concept of PHP. On the other hand as you said you provided small example, there may be other things in play. – Michal Brašna Feb 19 '14 at 15:09