Reputation: 3378
I always thought that static
variables were initialized once only and retained their values until the end of the script. However I came across a bug in my code, where a div
ID attribute is created by increasing a counter held in a static variable.
Here is a reproduction of the bug:
class Node {
var $child = array();
public function test () {
static $var = 0;
$var++;
echo $var,"\n";
foreach ( $this->child as $c )
$c->test();
}
public function add_child ( $c ) {
$this->child[] = $c;
}
}
class Tree extends Node {
public function __construct() {
parent::add_child( new Node() );
parent::add_child( new Node() );
}
}
$tree = new Tree();
$tree->test();
The above has 3 calls to test()
where the expected output should be
1 2 3
However, The variable static $var
is being initialized twice, producing output of:
1 1 2
Can anybody explain why this is happening?
Upvotes: 1
Views: 423
Reputation: 19989
That is some nasty code. When you are presented with issues like this, it's best to take a step back and consider another way. Otherwise when you come back in 6 months to add a new feature you will want to faceroll the keyboard.
Shot in the dark, looks like Late Static Binding (lsb) is at work here. The $tree object you instantiated created two node objects, each of which have a test() method, each of which have a static $var
bound to them.
$var value per call
$tree->test() $tree->child[0]->test() $tree->child[1]->test()
1 1 2
Notice that the $tree->child[1]->test()
call outputs a 2, this is because the static $var
variable bound itself to the $tree
object which was already incremented when you made the initial $tree->test()
call.
I added some output to your original code to demonstrate this point:
class Node {
public $id;
var $child = array();
public function test () {
static $var = 0;
$var++;
var_dump(sprintf('Id: %d | Var: %d', $this->id, $var));
foreach ( $this->child as $key => $c ) {
var_dump(sprintf('Container Object Id: %d | Child Id: %d | Var: %d', $this->id, $c->id, $var));
$c->test();
}
}
public function add_child ( $c ) {
$this->child[] = $c;
}
}
class Tree extends Node {
public function __construct() {
$node1 = new Node;
$node1->id = 1;
$node2 = new Node;
$node2->id = 2;
parent::add_child( $node1 );
parent::add_child( $node2 );
}
}
$tree = new Tree;
$tree->test();
// Output
// Conclusion: $var bound itself to $tree during runtime
string 'Id: 0 | Var: 1' (length=14)
string 'Container Object Id: 0 | Child Id: 1 | Var: 1' (length=45)
string 'Id: 1 | Var: 1' (length=14)
string 'Container Object Id: 0 | Child Id: 2 | Var: 1' (length=45)
string 'Id: 2 | Var: 2' (length=14)
Upvotes: 1
Reputation: 20486
It looks like the static variable $var
is being called by Tree::test()
and the child Node::test()
. Not sure of the best way to get around this. But what I did is make the Tree::add_child()
add a child object of class Tree
not class Node
.
class Node {
var $child = array();
public function test () {
static $var = 0;
$var++;
echo $var,"\n";
foreach ( $this->child as $c )
$c->test();
}
public function add_child ( $c ) {
$this->child[] = $c;
}
}
class Tree extends Node {
public static function createInitial() {
$tree = new Tree();
$tree->addChild();
$tree->addChild();
return $tree;
}
public function addChild() {
parent::add_child( new Tree() );
}
}
$tree = Tree::createInitial();
$tree->test();
Upvotes: 2