Twifty
Twifty

Reputation: 3378

static variable being initialized twice

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

Answers (2)

Mike Purcell
Mike Purcell

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

Sam
Sam

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

Related Questions