Подтвердить что ты не робот

Существует ли рациональное объяснение этого поведения 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
)

Есть ли для этого рациональное объяснение?

4b9b3361

Ответ 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", который показывает, что они не являются копиями или клонами.