Question

I created a simple class to manage a tree data structure which seemed to behave strangely (code to follow). When it became apparent that this was not a mistake by me I created a test case which produced equally puzzling behavior. This is the same in 5.3 and 5.4.

This is my test case:

<?php
class testcaseA {
    public function __construct($one=0, $two=1, $three=2) {
        $this->obj = new testcaseB($one++, $three, $two);
    }
    public function &get($what){
        return $this->obj->get($what);
    } 
}
class testcaseB {
    public function __construct($one, &$two, &$three) {
        $this->one=$one;
        $this->two=$two;
        $this->three=$three;
        echo "\$one={$one}";
    }
    public function &get($what){
        echo "<p>You asked for $what. $what ain't no country I ever heard of.<br />";
        echo "Check: [{$this->one}], {$this->two}, {$this->three}. Is this thing on?</p>";
        $this->obj[$what] = new testcaseB($this->one++,$this->two,$this->three);
        return $this->obj[$what];
    } 
}
ini_set('display_errors',1); 
error_reporting(E_ALL);
$bob = new testcaseA();
$bob->get("What")->get("Spam")->get("America");
$bob->get("What")->get("EU")->get("France");
echo "<pre>";
print_r($bob);

Now the output that I expected was to see the value for $one increment 1,2,3,1,2,3 and produce a tree shape.

This is the output I actually got:

$one=0

You asked for What. What ain't no country I ever heard of.
Check: [0], 2, 1. Is this thing on?
$one=0

You asked for Spam. Spam ain't no country I ever heard of.
Check: [0], 2, 1. Is this thing on?
$one=0

You asked for America. America ain't no country I ever heard of.
Check: [0], 2, 1. Is this thing on?
$one=0

You asked for What. What ain't no country I ever heard of.
Check: [1], 2, 1. Is this thing on?
$one=1

You asked for EU. EU ain't no country I ever heard of.
Check: [1], 2, 1. Is this thing on?
$one=1

You asked for France. France ain't no country I ever heard of.
Check: [1], 2, 1. Is this thing on?
$one=1

testcaseA Object
(
    [obj] => testcaseB Object
        (
            [one] => 2
            [two] => 2
            [three] => 1
            [obj] => Array
                (
                    [What] => testcaseB Object
                        (
                            [one] => 2
                            [two] => 2
                            [three] => 1
                            [obj] => Array
                                (
                                    [EU] => testcaseB Object
                                        (
                                            [one] => 2
                                            [two] => 2
                                            [three] => 1
                                            [obj] => Array
                                                (
                                                    [France] => testcaseB Object
                                                        (
                                                            [one] => 1
                                                            [two] => 2
                                                            [three] => 1
                                                        )

                                                )

                                        )

                                )

                        )

                )

        )

)

This confused me at first but I wondered if maybe the chained usage was setting values in a way I was not expecting.

So I tried this which I added after the previous code:

...same classes and initial code as before...
$a=$bob->get("What");
$b=$a->get("Spam");
$c=$b->get("America");
//$bob->get("What")
$d=$a->get("EU");
$e=$d->get("France");  
print_r($bob);

This produced a different but still unpredictable set of results.

You asked for What. What ain't no country I ever heard of.
Check: [2], 2, 1. Is this thing on?
$one=2

You asked for Spam. Spam ain't no country I ever heard of.
Check: [2], 2, 1. Is this thing on?
$one=2

You asked for America. America ain't no country I ever heard of.
Check: [2], 2, 1. Is this thing on?
$one=2

You asked for EU. EU ain't no country I ever heard of.
Check: [3], 2, 1. Is this thing on?
$one=3

You asked for France. France ain't no country I ever heard of.
Check: [3], 2, 1. Is this thing on?
$one=3testcaseA Object
(
    [obj] => testcaseB Object
        (
            [one] => 3
            [two] => 2
            [three] => 1
            [obj] => Array
                (
                    [What] => testcaseB Object
                        (
                            [one] => 4
                            [two] => 2
                            [three] => 1
                            [obj] => Array
                                (
                                    [Spam] => testcaseB Object
                                        (
                                            [one] => 3
                                            [two] => 2
                                            [three] => 1
                                            [obj] => Array
                                                (
                                                    [America] => testcaseB Object
                                                        (
                                                            [one] => 2
                                                            [two] => 2
                                                            [three] => 1
                                                        )

                                                )

                                        )

                                    [EU] => testcaseB Object
                                        (
                                            [one] => 4
                                            [two] => 2
                                            [three] => 1
                                            [obj] => Array
                                                (
                                                    [France] => testcaseB Object
                                                        (
                                                            [one] => 3
                                                            [two] => 2
                                                            [three] => 1
                                                        )

                                                )

                                        )

                                )

                        )

                )

        )

)

This is still not the behavior that I am after but it is closer. What I need is to use a chain of objects to traverse the tree (as with the first case), a pointer to the values $two and $three which in the real case are arrays and not swapped about. What I don't want to do is copy objects needlessly.

On the other hand I do need to have all the objects share a single pair of variables which they all use.

My guess is that the method get() could afford to be byval rather than byref although instinctively this seems wrong.

Can anyone explain what the $one value is doing?

Also can anyone help me understand the behavior of the first test case especially to the values in the array the first time?

UPDATE

Making use of the awesome suggestions our test case now looks like:

class testcaseA {
    public function __construct($one=0, $two=1, $three=2) {
        $this->obj = new testcaseB(++$one, $three, $two);
    }
    public function &get($what){
        return $this->obj->get($what);
    } 
}
class testcaseB {
    public function __construct($one, &$two, &$three) {
        $this->one=$one;
        $this->two=$two;
        $this->three=$three;
        echo "[New:\$one={$one}]:";
    }
    //public function &get($what){
    public function &get($what){
        //echo "<p>You asked for $what. $what ain't no country I ever heard of.<br />";
        echo "Get:{$what}:[{$this->one}]<br />";
        if(!isset($this->obj[$what])){
            $this->obj[$what] = new testcaseB(++$this->one,$this->two,$this->three);
        }
        return $this->obj[$what];
    } 
}
echo "STARTING:<br />";
ini_set('display_errors',1); 
error_reporting(E_ALL);
echo "REALLY STARTING:<br />";
echo "<pre>";
echo "<p>One at a time:</p>";
$bob = new testcaseA();
$a=$bob->get("What");
$b=$a->get("Spam");
$c=$b->get("America");
$d=$a->get("EU");
$e=$d->get("France"); 
echo "<br />";
print_r($bob); 
echo "<p>Chained:</p>";
$bobby = new testcaseA();
$bobby->get("What")->get("Spam")->get("America");
$bobby->get("What")->get("EU")->get("France");
echo "<br />";
print_r($bob);

The out put of which is:

STARTING:
REALLY STARTING:

One at a time:
[New:$one=1]:Get:What:[1]
[New:$one=2]:Get:Spam:[2]
[New:$one=3]:Get:America:[3]
[New:$one=4]:Get:EU:[3]
[New:$one=4]:Get:France:[4]
[New:$one=5]:
testcaseA Object
(
    [obj] => testcaseB Object
        (
            [one] => 2
            [two] => 2
            [three] => 1
            [obj] => Array
                (
                    [What] => testcaseB Object
                        (
                            [one] => 4
                            [two] => 2
                            [three] => 1
                            [obj] => Array
                                (
                                    [Spam] => testcaseB Object
                                        (
                                            [one] => 4
                                            [two] => 2
                                            [three] => 1
                                            [obj] => Array
                                                (
                                                    [America] => testcaseB Object
                                                        (
                                                            [one] => 4
                                                            [two] => 2
                                                            [three] => 1
                                                        )

                                                )

                                        )

                                    [EU] => testcaseB Object
                                        (
                                            [one] => 5
                                            [two] => 2
                                            [three] => 1
                                            [obj] => Array
                                                (
                                                    [France] => testcaseB Object
                                                        (
                                                            [one] => 5
                                                            [two] => 2
                                                            [three] => 1
                                                        )

                                                )

                                        )

                                )

                        )

                )

        )

)

Chained:
[New:$one=1]:Get:What:[1]
[New:$one=2]:Get:Spam:[2]
[New:$one=3]:Get:America:[3]
[New:$one=4]:Get:What:[2]
Get:EU:[3]
[New:$one=4]:Get:France:[4]
[New:$one=5]:
testcaseA Object
(
    [obj] => testcaseB Object
        (
            [one] => 2
            [two] => 2
            [three] => 1
            [obj] => Array
                (
                    [What] => testcaseB Object
                        (
                            [one] => 4
                            [two] => 2
                            [three] => 1
                            [obj] => Array
                                (
                                    [Spam] => testcaseB Object
                                        (
                                            [one] => 4
                                            [two] => 2
                                            [three] => 1
                                            [obj] => Array
                                                (
                                                    [America] => testcaseB Object
                                                        (
                                                            [one] => 4
                                                            [two] => 2
                                                            [three] => 1
                                                        )

                                                )

                                        )

                                    [EU] => testcaseB Object
                                        (
                                            [one] => 5
                                            [two] => 2
                                            [three] => 1
                                            [obj] => Array
                                                (
                                                    [France] => testcaseB Object
                                                        (
                                                            [one] => 5
                                                            [two] => 2
                                                            [three] => 1
                                                        )

                                                )

                                        )

                                )

                        )

                )

        )

)

The output numbers seem right but the $one are off in the stack.

Was it helpful?

Solution

If I understand what you are confused about (and I think I do) you have run into the subtle difference between pre- and post-increment. This can be most easily demonstrated in code:

$a = 1;
echo $a++; // 1
echo $a;   // 2

On the other hand:

$a = 1;
echo ++$a; // 2
echo $a;   // 2

Basically, by placing ++ before the value you increment, you will get the new value. By placing it after (as you have done) you will get the old value.

I think the key line in your code is this:

$this->obj[$what] = new testcaseB($this->one++,$this->two,$this->three);

...should be this:

$this->obj[$what] = new testcaseB(++$this->one,$this->two,$this->three);

With the post-increment, the initial value of $this->one will be carried through all subsequent iterations.

As a side note, I don't think you need to worry about returning by reference here, since all objects in PHP5 are passed by reference anyway.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top