Deixe-me prefixar isso dizendo que sei o que foreach
é, faz e como usá-lo. Esta pergunta diz respeito a como ela funciona sob o capô, e eu não quero nenhuma resposta na linha de "é assim que você faz um loop com uma matriz foreach
".
Por um longo tempo, presumi que foreach
funcionasse com o próprio array. Então eu encontrei muitas referências ao fato de que ele funciona com uma cópia da matriz e, desde então, assumi que este é o fim da história. Mas recentemente entrei em uma discussão sobre o assunto e, depois de um pouco de experimentação, descobri que isso não era de fato 100% verdade.
Deixe-me mostrar o que quero dizer. Para os seguintes casos de teste, trabalharemos com a seguinte matriz:
$array = array(1, 2, 3, 4, 5);
foreach ($array as $item) {
echo "$item\n";
$array[] = $item;
}
print_r($array);
/* Output in loop: 1 2 3 4 5
$array after loop: 1 2 3 4 5 1 2 3 4 5 */
Isso mostra claramente que não estamos trabalhando diretamente com a matriz de origem - caso contrário, o loop continuaria para sempre, pois estamos constantemente empurrando itens para a matriz durante o loop. Mas apenas para ter certeza de que este é o caso:
foreach ($array as $key => $item) {
$array[$key + 1] = $item + 2;
echo "$item\n";
}
print_r($array);
/* Output in loop: 1 2 3 4 5
$array after loop: 1 3 4 5 6 7 */
Isso confirma nossa conclusão inicial, estamos trabalhando com uma cópia da matriz de origem durante o loop, caso contrário veríamos os valores modificados durante o loop. Mas...
Se olharmos no manual , encontramos esta declaração:
Quando o foreach começa a executar pela primeira vez, o ponteiro interno da matriz é redefinido automaticamente para o primeiro elemento da matriz.
Certo ... isso parece sugerir que foreach
depende do ponteiro da matriz de origem. Mas acabamos de provar que não estamos trabalhando com a matriz de origem , certo? Bem, não inteiramente.
// Move the array pointer on one to make sure it doesn't affect the loop
var_dump(each($array));
foreach ($array as $item) {
echo "$item\n";
}
var_dump(each($array));
/* Output
array(4) {
[1]=>
int(1)
["value"]=>
int(1)
[0]=>
int(0)
["key"]=>
int(0)
}
1
2
3
4
5
bool(false)
*/
Portanto, apesar de não estarmos trabalhando diretamente com a matriz de origem, estamos trabalhando diretamente com o ponteiro da matriz de origem - o fato de o ponteiro estar no final da matriz no final do loop mostra isso. Exceto que isso não pode ser verdade - se fosse, o caso de teste 1 seria repetido para sempre.
O manual do PHP também declara:
Como o foreach depende do ponteiro interno da matriz, alterá-lo no loop pode levar a um comportamento inesperado.
Bem, vamos descobrir o que é esse "comportamento inesperado" (tecnicamente, qualquer comportamento é inesperado, já que eu não sei mais o que esperar).
foreach ($array as $key => $item) {
echo "$item\n";
each($array);
}
/* Output: 1 2 3 4 5 */
foreach ($array as $key => $item) {
echo "$item\n";
reset($array);
}
/* Output: 1 2 3 4 5 */
... nada de inesperado lá, na verdade, parece apoiar a teoria da "cópia da fonte".
A questão
O que está acontecendo aqui? Meu C-fu não é bom o suficiente para que eu possa extrair uma conclusão adequada simplesmente olhando o código-fonte PHP; eu apreciaria se alguém pudesse traduzi-lo para o inglês para mim.
Parece-me que foreach
funciona com uma cópia da matriz, mas define o ponteiro da matriz de origem para o final da matriz após o loop.
- Isso está correto e a história toda?
- Se não, o que está realmente fazendo?
- Existe alguma situação em que o uso de funções que ajustam o ponteiro da matriz (
each()
,reset()
et al.) Durante aforeach
possa afetar o resultado do loop?
foreach ($array as &$value)
) - O PHP precisa conhecer a posição atual na matriz original, mesmo que esteja realmente repetindo uma cópia.Respostas:
foreach
suporta iteração em três tipos diferentes de valores:Traversable
objetosA seguir, tentarei explicar com precisão como a iteração funciona em diferentes casos. De longe, o caso mais simples são os
Traversable
objetos, pois,foreach
essencialmente, esses são apenas açúcar de sintaxe para o código, ao longo destas linhas:Para classes internas, as chamadas de método reais são evitadas usando uma API interna que basicamente apenas reflete a
Iterator
interface no nível C.A iteração de matrizes e objetos simples é significativamente mais complicada. Antes de tudo, deve-se notar que, no PHP, "matrizes" são realmente dicionários ordenados e serão percorridos de acordo com essa ordem (que corresponde à ordem de inserção, desde que você não tenha usado algo assim
sort
). Isso se opõe à iteração pela ordem natural das chaves (como as listas em outros idiomas geralmente funcionam) ou por não ter nenhuma ordem definida (como os dicionários em outros idiomas costumam funcionar).O mesmo se aplica aos objetos, pois as propriedades do objeto podem ser vistas como outro dicionário (ordenado) de mapeamento de nomes de propriedades para seus valores, além de alguma manipulação de visibilidade. Na maioria dos casos, as propriedades do objeto não são realmente armazenadas dessa maneira bastante ineficiente. No entanto, se você começar a iterar sobre um objeto, a representação compactada normalmente usada será convertida em um dicionário real. Nesse ponto, a iteração de objetos simples se torna muito semelhante à iteração de matrizes (é por isso que não estou discutindo muito a iteração de objeto simples aqui).
Por enquanto, tudo bem. Iterar sobre um dicionário não pode ser muito difícil, certo? Os problemas começam quando você percebe que uma matriz / objeto pode mudar durante a iteração. Existem várias maneiras de isso acontecer:
foreach ($arr as &$v)
então$arr
será transformado em uma referência e você poderá alterá-lo durante a iteração.$ref =& $arr; foreach ($ref as $v)
O problema de permitir modificações durante a iteração é o caso em que o elemento em que você está atualmente é removido. Digamos que você use um ponteiro para acompanhar em qual elemento da matriz você está atualmente. Se esse elemento agora for liberado, você ficará com um ponteiro pendente (geralmente resultando em um segfault).
Existem diferentes maneiras de resolver esse problema. O PHP 5 e o PHP 7 diferem significativamente nesse aspecto e descreverei os dois comportamentos a seguir. O resumo é que a abordagem do PHP 5 foi bastante tola e levou a todos os tipos de problemas estranhos, enquanto a abordagem mais envolvida do PHP 7 resulta em um comportamento mais previsível e consistente.
Como última preliminar, deve-se notar que o PHP usa contagem de referência e cópia na gravação para gerenciar a memória. Isso significa que, se você "copiar" um valor, na verdade você apenas reutiliza o valor antigo e aumenta sua contagem de referência (refcount). Somente quando você realizar algum tipo de modificação, uma cópia real (chamada de "duplicação") será feita. Consulte Você está mentindo para obter uma introdução mais extensa sobre este tópico.
PHP 5
Ponteiro de matriz interno e HashPointer
As matrizes no PHP 5 têm um "ponteiro interno de matriz" (IAP) dedicado, que suporta corretamente modificações: Sempre que um elemento for removido, será verificado se o IAP aponta para esse elemento. Se isso acontecer, ele será avançado para o próximo elemento.
Embora
foreach
faça uso do IAP, há uma complicação adicional: existe apenas um IAP, mas uma matriz pode fazer parte de váriosforeach
loops:Para oferecer suporte a dois loops simultâneos com apenas um ponteiro interno de matriz,
foreach
execute as seguintes travessuras: Antes de o corpo do loop ser executado,foreach
faça backup de um ponteiro para o elemento atual e seu hash em um for-foreachHashPointer
. Depois que o corpo do loop for executado, o IAP retornará a esse elemento se ele ainda existir. Se, no entanto, o elemento tiver sido removido, usaremos apenas onde quer que o IAP esteja atualmente. Esse esquema funciona quase que meio que tipo de trabalho, mas há um monte de comportamento estranho que você pode obter dele, alguns dos quais eu demonstrarei abaixo.Duplicação de matriz
O IAP é um recurso visível de uma matriz (exposta através da
current
família de funções), pois essas alterações no IAP contam como modificações na semântica de copiar na gravação. Infelizmente, isso significa que,foreach
em muitos casos, é forçado a duplicar a matriz pela qual está iterando. As condições precisas são:refcount
for 1, a matriz não será compartilhada e podemos modificá-la diretamente.Se a matriz não for duplicada (is_ref = 0, refcount = 1), apenas sua
refcount
será incrementada (*). Além disso, seforeach
por referência for usada, a matriz (potencialmente duplicada) será transformada em referência.Considere este código como um exemplo em que ocorre duplicação:
Aqui,
$arr
será duplicado para impedir que as alterações do IAP$arr
vazem para$outerArr
. Em termos das condições acima, a matriz não é uma referência (is_ref = 0) e é usada em dois locais (refcount = 2). Esse requisito é lamentável e um artefato da implementação abaixo do ideal (não há preocupação de modificação durante a iteração aqui, portanto, não precisamos realmente usar o IAP em primeiro lugar).(*) Incrementar
refcount
aqui parece inócuo, mas viola a semântica de cópia na gravação (COW): Isso significa que vamos modificar o IAP de uma matriz refcount = 2, enquanto a COW determina que as modificações só podem ser executadas em refcount = 1 valores. Essa violação resulta em alteração de comportamento visível ao usuário (enquanto uma COW é normalmente transparente) porque a alteração de IAP na matriz iterada será observável - mas apenas até a primeira modificação não-IAP na matriz. Em vez disso, as três opções "válidas" seriam: a) duplicar sempre, b) não incrementar orefcount
e, assim, permitir que a matriz iterada seja arbitrariamente modificada no loop ou c) não usar o IAP (o PHP 7 solução).Ordem de avanço de posição
Há um último detalhe de implementação que você precisa conhecer para entender corretamente os exemplos de código abaixo. A maneira "normal" de percorrer alguma estrutura de dados seria algo parecido com isto no pseudocódigo:
No entanto
foreach
, sendo um floco de neve bastante especial, escolhe fazer as coisas de maneira ligeiramente diferente:Ou seja, o ponteiro da matriz já foi movido para frente antes da execução do corpo do loop. Isso significa que, enquanto o corpo do loop está trabalhando no elemento
$i
, o IAP já está no elemento$i+1
. Essa é a razão pela qual as amostras de código que mostram modificações durante a iteração sempre serãounset
o próximo elemento, em vez do atual.Exemplos: Seus casos de teste
Os três aspectos descritos acima devem fornecer uma impressão quase completa das idiossincrasias da
foreach
implementação e podemos seguir em frente para discutir alguns exemplos.O comportamento dos seus casos de teste é simples de explicar neste momento:
Nos casos de teste 1 e 2,
$array
começa com refcount = 1, portanto não será duplicado porforeach
: Apenas orefcount
é incrementado. Quando o corpo do loop modifica subsequentemente a matriz (que possui refcount = 2 nesse ponto), a duplicação ocorrerá nesse ponto. O Foreach continuará trabalhando em uma cópia não modificada de$array
.No caso de teste 3, mais uma vez a matriz não é duplicada, portanto,
foreach
será modificado o IAP da$array
variável. No final da iteração, o IAP é NULL (o que significa que a iteração foi concluída), o queeach
indica retornandofalse
.Em casos de ensaio 4 e 5 ambos
each
ereset
são funções por referência. O$array
tem umrefcount=2
quando é passado para eles, portanto, ele deve ser duplicado. Como talforeach
, estará trabalhando em uma matriz separada novamente.Exemplos: efeitos de
current
no foreachUma boa maneira de mostrar os vários comportamentos de duplicação é observar o comportamento da
current()
função dentro de umforeach
loop. Considere este exemplo:Aqui você deve saber que
current()
é uma função by-ref (na verdade: prefer-ref), mesmo que não modifique a matriz. Tem que ser para ser agradável com todas as outras funções, como asnext
que são todas por referência. A passagem por referência implica que o array deve ser separado e, portanto,$array
e oforeach-array
será diferente. A razão que você obter2
em vez de1
também é mencionado acima:foreach
avança o ponteiro do array antes de executar o código de utilizador, e não depois. Portanto, mesmo que o código esteja no primeiro elemento,foreach
já avançou o ponteiro para o segundo.Agora vamos tentar uma pequena modificação:
Aqui temos o caso is_ref = 1, para que o array não seja copiado (como acima). Mas agora que é uma referência, o array não precisa mais ser duplicado ao passar para a
current()
função by-ref . Assimcurrent()
eforeach
de trabalho na mesma matriz. Você ainda vê o comportamento de um por um, devido à maneira comoforeach
o ponteiro avança.Você obtém o mesmo comportamento ao fazer a iteração by-ref:
Aqui, a parte importante é que o foreach
$array
cria um is_ref = 1 quando é iterado por referência, portanto, basicamente, você tem a mesma situação acima.Outra pequena variação, desta vez, atribuiremos o array a outra variável:
Aqui, a refcount de
$array
é 2 quando o loop é iniciado, portanto, pela primeira vez, precisamos fazer a duplicação antecipadamente. Assim,$array
a matriz usada pelo foreach será completamente separada do início. É por isso que você obtém a posição do IAP onde quer que estivesse antes do loop (nesse caso, estava na primeira posição).Exemplos: modificação durante a iteração
Tentando explicar as modificações durante a iteração é onde todos os nossos problemas de foreach se originaram, por isso serve para considerar alguns exemplos para este caso.
Considere esses loops aninhados sobre a mesma matriz (onde a iteração by-ref é usada para garantir que realmente seja a mesma):
A parte esperada aqui é que
(1, 2)
está faltando na saída porque o elemento1
foi removido. O que provavelmente é inesperado é que o loop externo para após o primeiro elemento. Por que é que?A razão por trás disso é o hack do loop aninhado descrito acima: Antes de o corpo do loop ser executado, a posição atual do IAP e o hash são armazenados em backup em a
HashPointer
. Após o corpo do loop, ele será restaurado, mas apenas se o elemento ainda existir, caso contrário, a posição atual do IAP (seja ela qual for) será usada. No exemplo acima, esse é exatamente o caso: O elemento atual do loop externo foi removido e, portanto, será utilizado o IAP, que já foi marcado como concluído pelo loop interno!Outra consequência do
HashPointer
mecanismo de backup + restauração é que as alterações no IAP através dereset()
etc. geralmente não causam impactoforeach
. Por exemplo, o código a seguir é executado como sereset()
não estivesse presente:O motivo é que, embora
reset()
modifique temporariamente o IAP, ele será restaurado no elemento foreach atual após o corpo do loop. Para forçarreset()
a afetar o loop, é necessário remover adicionalmente o elemento atual, para que o mecanismo de backup / restauração falhe:Mas esses exemplos ainda são sensatos. A verdadeira diversão começa se você se lembrar que a
HashPointer
restauração usa um ponteiro para o elemento e seu hash para determinar se ele ainda existe. Mas: os hashes têm colisões e os ponteiros podem ser reutilizados! Isso significa que, com uma escolha cuidadosa de chaves de matriz, podemosforeach
acreditar que um elemento que foi removido ainda existe, para que ele pule diretamente para ele. Um exemplo:Aqui normalmente devemos esperar a saída de
1, 1, 3, 4
acordo com as regras anteriores. Como o que acontece é que'FYFY'
tem o mesmo hash que o elemento removido'EzFY'
e o alocador reutiliza o mesmo local de memória para armazenar o elemento. Assim, o foreach acaba pulando diretamente para o elemento recém-inserido, cortando assim o loop.Substituindo a entidade iterada durante o loop
Um último caso estranho que eu gostaria de mencionar, é que o PHP permite que você substitua a entidade iterada durante o loop. Portanto, você pode começar a iterar em uma matriz e substituí-la por outra matriz no meio. Ou comece a iterar em uma matriz e substitua-a por um objeto:
Como você pode ver neste caso, o PHP começará a iterar a outra entidade desde o início, depois que a substituição acontecer.
PHP 7
Iteradores hashtable
Se você ainda se lembra, o principal problema com a iteração de matriz era como lidar com a remoção de elementos no meio da iteração. O PHP 5 usou um único ponteiro interno de matriz (IAP) para esse fim, que foi um pouco abaixo do ideal, pois um ponteiro de matriz teve que ser esticado para suportar vários loops foreach simultâneos e interação com
reset()
etc., além disso.O PHP 7 usa uma abordagem diferente, a saber, ele suporta a criação de uma quantidade arbitrária de iteradores de hashtable externos seguros. Esses iteradores precisam ser registrados na matriz, a partir de então eles têm a mesma semântica que o IAP: Se um elemento da matriz for removido, todos os iteradores de hashtable que apontam para esse elemento serão avançados para o próximo elemento.
Isto significa que
foreach
já não usam o IAP em tudo . Oforeach
loop não terá absolutamente nenhum efeito nos resultados decurrent()
etc. e seu próprio comportamento nunca será influenciado por funções comoreset()
etc.Duplicação de matriz
Outra mudança importante entre o PHP 5 e o PHP 7 está relacionada à duplicação de matrizes. Agora que o IAP não é mais usado, a iteração por matriz de valor fará apenas um
refcount
incremento (em vez de duplicar a matriz) em todos os casos. Se a matriz for modificada durante oforeach
loop, nesse ponto ocorrerá uma duplicação (de acordo com a cópia na gravação) eforeach
continuará trabalhando na matriz antiga.Na maioria dos casos, essa alteração é transparente e não tem outro efeito senão melhor desempenho. No entanto, há uma ocasião em que resulta em comportamento diferente, a saber, o caso em que a matriz era uma referência anterior:
Anteriormente, a iteração por valor das matrizes de referência era casos especiais. Nesse caso, não ocorreu duplicação; portanto, todas as modificações da matriz durante a iteração seriam refletidas pelo loop. No PHP 7, este caso especial se foi: Uma iteração por valor de uma matriz sempre continuará trabalhando nos elementos originais, desconsiderando qualquer modificação durante o loop.
Obviamente, isso não se aplica à iteração por referência. Se você iterar por referência, todas as modificações serão refletidas pelo loop. Curiosamente, o mesmo se aplica à iteração por valor de objetos simples:
Isso reflete a semântica de manipulação de objetos (ou seja, eles se comportam como referência mesmo em contextos de valor).
Exemplos
Vamos considerar alguns exemplos, começando com seus casos de teste:
Os casos de teste 1 e 2 mantêm a mesma saída: a iteração da matriz por valor sempre continua trabalhando nos elementos originais. (Nesse caso, o
refcounting
comportamento par e duplicação é exatamente o mesmo entre o PHP 5 e o PHP 7).O caso de teste 3 é alterado:
Foreach
não usa mais o IAP, portantoeach()
não é afetado pelo loop. Ele terá a mesma saída antes e depois.Os casos de teste 4 e 5 permanecem os mesmos:
each()
ereset()
duplicam a matriz antes de alterar o IAP, enquantoforeach
ainda usam a matriz original. (Não que a alteração do IAP tenha importado, mesmo que a matriz tenha sido compartilhada.)O segundo conjunto de exemplos estava relacionado ao comportamento de
current()
diferentesreference/refcounting
configurações. Isso não faz mais sentido, pois nãocurrent()
é totalmente afetado pelo loop, portanto, seu valor de retorno sempre permanece o mesmo.No entanto, obtemos algumas mudanças interessantes ao considerarmos modificações durante a iteração. Espero que você ache o novo comportamento mais saudável. O primeiro exemplo:
Como você pode ver, o loop externo não é mais interrompido após a primeira iteração. O motivo é que os dois loops agora têm iteradores de hashtable totalmente separados e não há mais nenhuma contaminação cruzada de ambos os loops através de um IAP compartilhado.
Outro caso estranho de borda corrigido agora é o efeito estranho que você obtém ao remover e adicionar elementos que possuem o mesmo hash:
Anteriormente, o mecanismo de restauração do HashPointer pulava diretamente para o novo elemento porque "parecia" o mesmo que o elemento removido (devido à colisão de hash e ponteiro). Como não confiamos mais no hash do elemento para nada, isso não é mais um problema.
fonte
$foo = $array
antes do laço;)Bucket
s são parte de uma lista duplamente ligado para colisões de hash e também parte de uma lista duplamente ligada por ordem;)iterate($outerArr);
e não emiterate($arr);
algum lugar.No exemplo 3, você não modifica a matriz. Em todos os outros exemplos, você modifica o conteúdo ou o ponteiro interno da matriz. Isso é importante quando se trata de matrizes PHP devido à semântica do operador de atribuição.
O operador de atribuição para as matrizes no PHP funciona mais como um clone lento. Atribuir uma variável a outra que contém uma matriz clonará a matriz, diferente da maioria dos idiomas. No entanto, a clonagem real não será feita, a menos que seja necessário. Isso significa que o clone ocorrerá somente quando qualquer uma das variáveis for modificada (copiar na gravação).
Aqui está um exemplo:
Voltando aos seus casos de teste, você pode facilmente imaginar que
foreach
cria algum tipo de iterador com uma referência à matriz. Esta referência funciona exatamente como a variável$b
no meu exemplo. No entanto, o iterador e a referência permanecem ativos apenas durante o loop e, em seguida, ambos são descartados. Agora você pode ver que, em todos os casos, exceto 3, a matriz é modificada durante o loop, enquanto essa referência extra está ativa. Isso aciona um clone, e isso explica o que está acontecendo aqui!Aqui está um excelente artigo para outro efeito colateral desse comportamento de copiar na gravação: O operador ternário do PHP: Rápido ou não?
fonte
each()
no final do primeiro caso de teste, onde vemos que o ponteiro da matriz original aponta para o segundo elemento, uma vez que a matriz foi modificada durante a primeira iteração. Isso também parece demonstrar queforeach
move o ponteiro da matriz antes de executar o bloco de código do loop, o que eu não esperava - eu pensaria que isso seria feito no final. Muito obrigado, isso esclarece muito bem para mim.Alguns pontos a serem observados ao trabalhar com
foreach()
:a)
foreach
trabalha na cópia prospectada da matriz original. Isso significaforeach()
que o armazenamento de dados será compartilhado até ou a menos que umprospected copy
não seja criado para cada comentário do Notes / usuário .b) O que desencadeia uma cópia prospectada ? Uma cópia prospectada é criada com base na política de
copy-on-write
, ou seja, sempre que uma matriz passadaforeach()
é alterada, um clone da matriz original é criado.c) A matriz original e o
foreach()
iterador terãoDISTINCT SENTINEL VARIABLES
, ou seja, uma para a matriz original e outra paraforeach
; veja o código de teste abaixo. SPL , Iteradores e Array Iterator .Pergunta sobre estouro de pilha Como garantir que o valor seja redefinido em um loop 'foreach' no PHP? aborda os casos (3,4,5) da sua pergunta.
O exemplo a seguir mostra que each () e reset () NÃO afetam
SENTINEL
variáveis(for example, the current index variable)
doforeach()
iterador.Resultado:
fonte
foreach
opera em uma cópia potencial da matriz, mas não faz a cópia real, a menos que seja necessário.foreach
está copiando a matriz 100% do tempo. Estou ansioso para saber. Obrigado por você comentafor
ouforeach
. Você não verá nenhuma diferença significativa entre os dois, porque uma cópia real não ocorre.SHARED data storage
reserva até ou a menoscopy-on-write
, mas (do meu trecho de código) é evidente que sempre haverá DOIS conjuntos deSENTINEL variables
um para ooriginal array
e para outroforeach
. Graças Isso faz sentidoNOTA PARA PHP 7
Para atualizar esta resposta, pois ela ganhou popularidade: Esta resposta não se aplica mais a partir do PHP 7. Conforme explicado em " Alterações incompatíveis com versões anteriores ", no PHP 7 foreach funciona na cópia da matriz, portanto, quaisquer alterações na própria matriz não são refletidos no loop foreach. Mais detalhes no link.
Explicação (citação de php.net ):
Portanto, no seu primeiro exemplo, você tem apenas um elemento na matriz e, quando o ponteiro é movido, o próximo elemento não existe; portanto, depois de adicionar um novo elemento, cada um deles termina porque ele já "decidiu" que ele é o último elemento.
No seu segundo exemplo, você começa com dois elementos, e o loop foreach não está no último elemento; portanto, ele avalia a matriz na próxima iteração e, portanto, percebe que há um novo elemento na matriz.
Eu acredito que tudo isso é consequência de Em cada iteração, parte da explicação na documentação, o que provavelmente significa que
foreach
faz toda a lógica antes de chamar o código{}
.Caso de teste
Se você executar isso:
Você obterá esta saída:
O que significa que ele aceitou a modificação e passou por ela porque foi modificada "a tempo". Mas se você fizer isso:
Você vai ter:
O que significa que a matriz foi modificada, mas como a modificamos quando o elemento
foreach
já estava no último elemento da matriz, ela "decidiu" não fazer mais um loop e, mesmo que adicionássemos novo elemento, adicionamos "tarde demais" e não foi repetido.Explicação detalhada pode ser lida em Como o PHP 'foreach' realmente funciona? o que explica os internos por trás desse comportamento.
fonte
Conforme a documentação fornecida pelo manual do PHP.
Então, como no seu primeiro exemplo:
$array
tem apenas um único elemento; portanto, de acordo com a execução do foreach, 1 atribuo a$v
e ele não tem nenhum outro elemento para mover o ponteiroMas no seu segundo exemplo:
$array
tem dois elementos, então agora $ array avalia os índices zero e move o ponteiro por um. Para a primeira iteração do loop, adicionada$array['baz']=3;
como passagem por referência.fonte
Ótima pergunta, porque muitos desenvolvedores, mesmo os mais experientes, ficam confusos com a maneira como o PHP lida com matrizes em loops foreach. No loop foreach padrão, o PHP faz uma cópia da matriz usada no loop. A cópia é descartada imediatamente após o término do loop. Isso é transparente na operação de um loop foreach simples. Por exemplo:
Isso gera:
Portanto, a cópia é criada, mas o desenvolvedor não percebe, porque a matriz original não é referenciada no loop ou após a conclusão do loop. No entanto, quando você tenta modificar os itens em um loop, descobre que eles não são modificados quando você termina:
Isso gera:
Quaisquer alterações no original não podem ser avisos, na verdade não há alterações no original, mesmo que você tenha atribuído claramente um valor a $ item. Isso ocorre porque você está operando no item $, como aparece na cópia do conjunto de $ que está sendo trabalhado. Você pode substituir isso pegando $ item por referência, da seguinte maneira:
Isso gera:
Portanto, é evidente e observável, quando $ item é operado por referência, as alterações feitas em $ item são feitas nos membros do conjunto $ original. Usar $ item por referência também impede que o PHP crie a cópia da matriz. Para testar isso, primeiro mostraremos um script rápido demonstrando a cópia:
Isso gera:
Como é mostrado no exemplo, o PHP copiou o $ set e o usou para fazer um loop, mas quando $ set foi usado dentro do loop, o PHP adicionou as variáveis à matriz original, não à matriz copiada. Basicamente, o PHP está usando apenas a matriz copiada para a execução do loop e a atribuição do item $. Por esse motivo, o loop acima é executado apenas três vezes e, a cada vez, acrescenta outro valor ao final do conjunto $ original, deixando o conjunto original com 6 elementos, mas nunca inserindo um loop infinito.
No entanto, e se tivéssemos usado $ item por referência, como mencionei antes? Um único caractere adicionado ao teste acima:
Resultados em um loop infinito. Observe que, na verdade, é um loop infinito, você terá que matar o script sozinho ou esperar que o sistema operacional fique sem memória. Eu adicionei a seguinte linha ao meu script para que o PHP fique sem memória muito rapidamente, sugiro que você faça o mesmo se estiver executando esses testes de loop infinito:
Portanto, neste exemplo anterior, com o loop infinito, vemos a razão pela qual o PHP foi escrito para criar uma cópia da matriz a ser repetida. Quando uma cópia é criada e usada apenas pela estrutura da própria construção do loop, a matriz permanece estática durante toda a execução do loop, para que você nunca tenha problemas.
fonte
O loop foreach do PHP pode ser usado com
Indexed arrays
,Associative arrays
eObject public variables
.No loop foreach, a primeira coisa que o php faz é que ele cria uma cópia da matriz que deve ser iterada. O PHP itera sobre esse novo
copy
da matriz e não o original. Isso é demonstrado no exemplo abaixo:Além disso, o php permite usar
iterated values as a reference to the original array value
também. Isso é demonstrado abaixo:Nota: Não permite
original array indexes
ser usado comoreferences
.Fonte: http://dwellupper.io/post/47/understanding-php-foreach-loop-with-examples
fonte
Object public variables
está errado ou, na melhor das hipóteses, enganoso. Você não pode usar um objeto em uma matriz sem a interface correta (por exemplo, Traversible) e, quando o faz,foreach((array)$obj ...
trabalha de fato com uma matriz simples, não mais com um objeto.