PHP 5:通过 ref 方法返回会产生意外的结果


PHP 5: return by ref method produces unexpected results

我创建了一个简单的类来管理一个看起来行为奇怪的树数据结构(代码如下)。当很明显这不是我的错误时,我创建了一个测试用例,产生了同样令人费解的行为。这在 5.3 和 5.4 中是相同的。

这是我的测试用例:

<?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);

现在,我期望的输出是查看增量 1,2,3,1,2,3 的值$one并生成树形。

这是我实际得到的输出:

$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
                                                        )
                                                )
                                        )
                                )
                        )
                )
        )
)

起初这让我感到困惑,但我想知道链式用法是否以我意想不到的方式设置值。

所以我尝试了这个,我在上一个代码之后添加了这个:

...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);

这产生了一组不同但仍然不可预测的结果。

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
                                                        )
                                                )
                                        )
                                )
                        )
                )
        )
)

这仍然不是我所追求的行为,但它更接近。我需要的是使用对象链来遍历树(与第一种情况一样),指向值$two和$three的指针,这些值在实际情况下是数组而不是交换。我不想做的是不必要地复制对象。

另一方面,我确实需要让所有对象共享一对它们都使用的变量。

我的猜测是,get()可以承受的方法byval而不是byref尽管本能地这似乎是错误的。

谁能解释一下$one值在做什么?

还有谁能帮助我了解第一个测试用例的行为,尤其是第一次对数组中的值?

更新

利用我们的测试用例现在的出色建议:

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);

其输出是:

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
                                                        )
                                                )
                                        )
                                )
                        )
                )
        )
)

输出数字似乎正确,但堆栈中的$one关闭。

如果我理解你对什么感到困惑(我想我确实如此),你已经遇到了前增量和后增量之间的微妙区别。这可以在代码中最容易地演示:

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

另一方面:

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

基本上,通过将++放在您递增的值之前,您将获得新值。通过将其放在(就像您所做的那样)之后,您将获得旧值。

我认为您的代码中的关键行是这样的:

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

。应该是这样的:

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

通过后增量,$this->one的初始值将贯穿所有后续迭代。

作为旁注,我认为您不需要担心此处通过引用返回,因为 PHP5 中的所有对象无论如何都是通过引用传递的。