PHP 5: возврат по методу ref дает неожиданные результаты

Я создал простой класс для управления древовидной структурой данных, которая казалась странной (код для подражания). Когда стало очевидно, что это не было моей ошибкой, я создал контрольный пример, который произвел столь же загадочное поведение. Это то же самое в 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 с шагом 1,2,3,1,2,3 и создадим форму дерева.

Это вывод, который я на самом деле получил:

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

                                                )

                                        )

                                )

                        )

                )

        )

)

Это все еще не то поведение, за которым я следую, но оно ближе. Мне нужно использовать цепочку объектов для обхода дерева (как в первом случае), указатель на значения $ два и $ три, которые в реальном случае являются массивами и не обмениваются. Я не хочу копировать объекты без необходимости.

С другой стороны, мне нужно, чтобы все объекты имели общую пару переменных, которые они все используют.

Я думаю, что метод 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 выключены в стеке.

1 ответ

Решение

Если я понимаю, что вас смущает (и я думаю, что понимаю), вы натолкнетесь на тонкую разницу между до и после приращения. Это легче всего продемонстрировать в коде:

$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 все равно передаются по ссылке.

Другие вопросы по тегам