Existe uma explicação racional para este comportamento de chamada por valor do PHP? Ou bug do PHP?

PHP 5.5.12. Considere isto:

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

Isto, conforme esperado, produz:

 Array ( [0] => aq [1] => bq [2] => cq ) 

Agora considere:

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

Esta saída:

 Array ( [0] => aq [1] => bq [2] => cq ) 

(!) Mas espere um minuto. $ a não está sendo aprovado por referência. O que significa que eu deveria estar recebendo uma cópia de z (), que seria modificada, e $ a deveria ser deixado sozinho.

Mas o que acontece quando forçamos o PHP a fazer sua mágica de cópia em gravação:

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

Para isso, obtemos o que eu esperaria:

 Array ( [0] => a [1] => b [2] => c ) 

EDITAR: mais um exemplo …

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

Isso funciona como esperado:

 Array ( [0] => a [1] => b [2] => c ) 

Existe uma explicação racional para isso?

Atualizar

O bug 67633 foi aberto para resolver esse problema. O comportamento foi alterado por este commit em um esforço para remover restrições de referência do foreach.


A partir desta saída 3v4l, você pode ver claramente que esse comportamento mudou ao longo do tempo:

Atualização 2

Corrigido com este commit ; Isso ficará disponível em 5.5.18 e 5.6.2.

PHP 5.4

Antes do PHP 5.5, seu código realmente aumentaria um erro fatal:

 Fatal error: Cannot create references to elements of a temporary array expression 

PHP 5.5 – 5.6

Essas versões não executam copy-on-write quando o resultado da function é usado diretamente dentro do bloco foreach . Como tal, a matriz original agora é usada e as mudanças nos elementos são permanentes.

Eu pessoalmente sinto que isso é um bug ; o copy-on-write deveria ter ocorrido.

PHP> 5.6

No ramo phpng , que provavelmente se tornará a base de uma próxima versão principal, os arrays constantes serão tornados imutáveis ​​para que a cópia em gravação seja realizada corretamente somente neste caso. Declarar a matriz como abaixo exibirá o mesmo problema com o phpng:

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

Prova

Hack (HHVM)

Somente o Hack lida com a situação corretamente como está atualmente.

O caminho certo

A maneira documentada de usar o resultado da function por referência é esta:

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

Demo

Outras correções

Para evitar a alteração da matriz original, por enquanto, você tem as seguintes opções:

  1. Atribua o resultado da function a uma variável temporária antes do foreach ;
  2. Não use referências;
  3. Mude para Hack.

Neste exemplo, a function z não faz nada. Não copia ou clona nada, portanto, a resposta de z () será a mesma coisa que a não chamada. Você está simplesmente retornando o object passado e, portanto, a resposta é conforme o esperado.

  

Isso é mais fácil de demonstrar usando objects à medida que eles recebem um ID do sistema:

 name = 'foo'; function z($a) { $a->name = 'bar'; return $a; } var_dump($obj); var_dump(z($obj)); 

A saída para isso é:

 object(stdClass)#1 (1) { ["name"]=> string(3) "foo" } object(stdClass)#1 (1) { ["name"]=> string(3) "bar" } 

Ambos os objects possuem o ID como "1", o que mostra que não são cópias ou clones.