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

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

    Related of "Существует ли рациональное объяснение этого поведения PHP по поведению? Или PHP ошибка?"

    Обновить

    Для решения этой проблемы был открыт ошибка 67633 . Это поведение было изменено этим фиксатором, чтобы устранить ссылочные ограничения из foreach.


    Из этого вывода 3v4l вы можете ясно видеть, что такое поведение со временем изменилось:

    Обновление 2

    Исправлено с этим фиксацией ; это станет доступно в 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 . Таким образом, исходный массив теперь используется, и изменения в элементах являются постоянными.

    Я лично считаю, что это ошибка ; должна быть сделана копия-на-запись.

    PHP> 5.6

    В ветке phpng , которая, вероятно, станет основой следующей крупной версии, постоянные массивы становятся неизменными, поэтому копирование на запись выполняется правильно только в этом случае. Объявление массива, как показано ниже, будет иметь такую ​​же проблему с phpng:

     $foo = 'b'; $a = ['a', $foo, 'b']; 

    доказательство

    Hack (HHVM)

    Только Hack правильно обрабатывает ситуацию, как она сейчас стоит.

    Правильный путь

    Документированный способ использования результата функции по ссылке заключается в следующем:

     $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; } 

    демонстрация

    Другие исправления

    Чтобы избежать изменения исходного массива, на данный момент у вас есть следующие параметры:

    1. Назначьте результат функции во временную переменную перед foreach ;
    2. Не используйте ссылки;
    3. Переключитесь на Hack.

    В этом примере функция 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», который показывает, что они не являются копиями или клонами.

    В вашем коде

     <?php $a = [ 'a', 'b', 'c' ]; foreach(z($a) as &$x) { $x .= 'q'; } print_r($a); function z($a) { return $a; } 

    Поистине странно мне сослаться на временное выражение. это &$x to z($a) потому что мой PHP-сервер этого не допускает.

    Но если ваш PHP-сервер действительно может это сделать, мое предложение, пожалуйста, избегайте использования имени параметра, такого же, как и любая глобальная переменная. В вашем случае $a является глобальной переменной области <?php script. Мое объяснение состоит в том, что речь идет о смешанном глобальном имени переменной с именем параметра. Поэтому, когда вы вызываете return $a; в вашей функции он даст глобальную переменную вместо значения параметра, которое вы ввели. Это значение все еще дает прямую ссылку переменной $a в функции foreach() .

    Обычно у меня есть имя префикса для любого имени параметров для моих функций, чтобы избежать смешивания с глобальными или локальными переменными. Обычно я использую $p_ в качестве префикса, поэтому попробуйте использовать function z($p_a) вместо function z($a) и увидеть результат.