Существует ли рациональное объяснение этого поведения PHP по поведению? Или PHP ошибка?
PHP 5.5.12. Рассмотрим это:
<?php
$a = [ 'a', 'b', 'c' ];
foreach($a as &$x) {
$x .= 'q';
}
print_r($a);
Это, как и ожидалось, выходы:
Array
(
[0] => aq
[1] => bq
[2] => cq
)
Теперь рассмотрим:
<?php
$a = [ 'a', 'b', 'c' ];
foreach(z($a) as &$x) {
$x .= 'q';
}
print_r($a);
function z($a)
{
return $a;
}
Выводится:
Array
(
[0] => aq
[1] => bq
[2] => cq
)
(!) Но подождите минуту. $a не передается по ссылке. Это означает, что я должен получить копию обратно из z(), которая будет изменена, а $a следует оставить в покое.
Но что происходит, когда мы заставляем PHP делать свою магию копирования на запись:
$a = [ 'a', 'b', 'c' ];
foreach(z($a) as &$x) {
$x .= 'q';
}
print_r($a);
function z($a)
{
$a[0] .= 'x';
return $a;
}
Для этого получаем то, что я ожидаю:
Array
(
[0] => a
[1] => b
[2] => c
)
EDIT: Еще один пример...
$a = [ 'a', 'b', 'c' ];
$b = z($a);
foreach($b as &$x) {
$x .= 'q';
}
print_r($a);
function z($a)
{
return $a;
}
Это работает как ожидалось:
Array
(
[0] => a
[1] => b
[2] => c
)
Есть ли для этого рациональное объяснение?
Ответы
Ответ 1
Update
Bug 67633 был открыт для решения этой проблемы. Поведение было изменено этим фиксатором в попытке удалить ссылочные ограничения из foreach.
Из этот вывод 3v4l вы можете ясно видеть, что это изменение со временем изменилось:
Обновление 2
Исправлено с помощью this commit; это станет доступно в 5.5.18 и 5.6.2.
PHP 5.4
До PHP 5.5 ваш код действительно вызвал бы фатальную ошибку:
Fatal error: Cannot create references to elements of a temporary array expression
PHP 5.5 - 5.6
Эти версии не выполняют copy-on-write, когда результат функции используется непосредственно внутри блока foreach
. Таким образом, исходный массив теперь используется, и изменения в элементах являются постоянными.
Мне лично кажется, что это ошибка; copy-on-write должно было состояться.
PHP > 5.6
В phpng branch, который, вероятно, станет основой следующей крупной версии, постоянные массивы становятся неизменными, поэтому копирование -write корректно выполняется только в этом случае. Объявление массива, как показано ниже, будет иметь ту же проблему с phpng:
$foo = 'b';
$a = ['a', $foo, 'b'];
Доказательство
Hack (HHVM)
Только Hack обрабатывает ситуацию правильно, как она в настоящее время стоит.
Правильный путь
documented способ использования результата функции по ссылке:
$a = [ 'a', 'b', 'c' ];
foreach(z($a) as &$x) {
$x .= 'q';
}
print_r($a);
// indicate that this function returns by reference
// and its argument must be a reference too
function &z(&$a)
{
return $a;
}
Демо
Другие исправления
Чтобы избежать изменения исходного массива, на данный момент у вас есть следующие параметры:
- Назначить результат функции во временную переменную до
foreach
;
- Не используйте ссылки;
- Переключиться на Hack.
Ответ 2
В этом примере функция z ничего не делает. Он не копирует и не клонирует что-либо, поэтому ответ от z() будет таким же, как и не называть вообще. Вы просто возвращаете переданный объект и, следовательно, ответ соответствует ожидаемому.
<?php
$a = [ 'a', 'b', 'c' ];
foreach(z($a) as &$x) {
$x .= 'q';
}
print_r($a);
function z($a)
{
return $a;
}
Thiis проще продемонстрировать использование объектов, поскольку им присваивается идентификатор системы:
<?php
$obj = new stdClass();
$obj->name = 'foo';
function z($a)
{
$a->name = 'bar';
return $a;
}
var_dump($obj);
var_dump(z($obj));
Выход для этого:
object(stdClass)#1 (1) {
["name"]=>
string(3) "foo"
}
object(stdClass)#1 (1) {
["name"]=>
string(3) "bar"
}
Оба объекта имеют идентификатор "1", который показывает, что они не являются копиями или клонами.