The code below illustrates the strange behaviour of PHP references:
<?php
function this_works()
{
$root = array('name'=>'root', 'children'=>array());
$level_1 = array('name'=>'level_1', 'children'=>array());
$item1 = array('name'=>'level_2_1', 'children'=>array());
$item2 = array('name'=>'level_2_2', 'children'=>array());
$croot = &$root;
$croot['children'][] = &$level_1;
$croot = &$level_1;
$croot['children'][] = &$item1;
$croot['children'][] = &$item2;
$croot = &$root;
print_r($croot);
}
function this_fails()
{
$root = array('name'=>'root', 'children'=>array());
$level_1 = array('name'=>'level_1', 'children'=>array());
$item1 = array('name'=>'level_2_1', 'children'=>array());
$item2 = array('name'=>'level_2_2', 'children'=>array());
$croot = &$root;
$stack = array();
$croot['children'][] = &$level_1;
$crootref = &$croot;
array_push($stack, $crootref);
$croot = &$level_1;
$croot['children'][] = &$item1;
$croot['children'][] = &$item2;
# this works, assignment below - doesn't... WHY?
#$x = array_pop($stack);
#var_dump($x);
$croot = array_pop($stack);
print_r($croot);
}
this_works();
echo "------------------\n";
this_fails();
?>
First function provides expected results,
while the second fails and claims about recursion loop:
Array
(
[name] => root
[children] => Array
(
[0] => Array
(
[name] => level_1
[children] => Array
(
[0] => Array
(
[name] => level_2_1
[children] => Array
(
)
)
[1] => Array
(
[name] => level_2_2
[children] => Array
(
)
)
)
)
)
)
------------------
Array
(
[name] => root
[children] => Array
(
[0] => Array
(
[name] => root
[children] => Array
*RECURSION*
)
)
)
What is strange, is that if in the second function, intermediate
variable will be used to get value from stack, results are OK again.
I don’t understand what is going on. How do I get root element as
a child of itself many times due to one assinment?
Originally, I needed to build the tree from XML (using sax parser)
and intented to have ‘current root’ that points to tree node at
the current level and push/pop it to/from stack and add child elements to it,
but, surprisingly, I failed to implement this scheme due to issues demonstrated
by two functions above.
So, what is wrong with such approach?
One picture worths 1000 words.
It took some time to understand
what’s exactly is going on.
I had to use Xdebug to dump internal
data properly and see refcounts
and effects of copy on write.
The issue with the code in 1st post
is that assignment
$croot = array_pop($stack);
when croot is ‘level_1’ is done by copy,
i.e. elements of croot (same as level_1)
are populated with data from stack and
croot is not the same as original root after this operation.
The image will explain better.