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