Permítanme ponerle un prefijo diciendo que sé qué foreach
es, qué hace y cómo usarlo. Esta pregunta se refiere a cómo funciona bajo el capó, y no quiero ninguna respuesta en la línea de "así es como se repite una matriz con foreach
".
Durante mucho tiempo supuse que foreach
funcionaba con la matriz en sí. Luego encontré muchas referencias al hecho de que funciona con una copia de la matriz, y desde entonces he asumido que este es el final de la historia. Pero recientemente tuve una discusión sobre el asunto, y después de un poco de experimentación descubrí que esto no era 100% cierto.
Déjame mostrarte lo que quiero decir. Para los siguientes casos de prueba, trabajaremos con la siguiente 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 */
Esto muestra claramente que no estamos trabajando directamente con la matriz fuente; de lo contrario, el ciclo continuaría para siempre, ya que constantemente estamos empujando elementos a la matriz durante el ciclo. Pero solo para estar seguro de que este es el 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 */
Esto respalda nuestra conclusión inicial, estamos trabajando con una copia de la matriz fuente durante el ciclo, de lo contrario veríamos los valores modificados durante el ciclo. Pero...
Si miramos en el manual , encontramos esta declaración:
Cuando foreach comienza a ejecutarse por primera vez, el puntero interno de la matriz se restablece automáticamente al primer elemento de la matriz.
Correcto ... esto parece sugerir que se foreach
basa en el puntero de matriz de la matriz fuente. Pero acabamos de demostrar que no estamos trabajando con la matriz fuente , ¿verdad? Bueno, no del todo.
// 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)
*/
Entonces, a pesar del hecho de que no estamos trabajando directamente con la matriz fuente, estamos trabajando directamente con el puntero de la matriz fuente; el hecho de que el puntero esté al final de la matriz al final del ciclo lo demuestra. Excepto que esto no puede ser cierto; si lo fuera, el caso de prueba 1 se repetiría para siempre.
El manual de PHP también establece:
Como foreach se basa en el puntero interno de la matriz, cambiarlo dentro del ciclo puede conducir a un comportamiento inesperado.
Bueno, descubramos qué es ese "comportamiento inesperado" (técnicamente, cualquier comportamiento es inesperado ya que ya no sé qué 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 tan inesperado allí, de hecho parece apoyar la teoría de la "copia de la fuente".
La pregunta
¿Que esta pasando aqui? Mi C-fu no es lo suficientemente bueno para poder extraer una conclusión adecuada simplemente mirando el código fuente de PHP, agradecería que alguien pudiera traducirlo al inglés por mí.
Me parece que foreach
funciona con una copia de la matriz, pero establece el puntero de la matriz de origen al final de la matriz después del bucle.
- ¿Es esto correcto y toda la historia?
- Si no, ¿qué está haciendo realmente?
- ¿Existe alguna situación en la que el uso de funciones que ajustan el puntero de la matriz (
each()
,reset()
et al.) Durante un procesoforeach
pueda afectar el resultado del ciclo?
foreach ($array as &$value)
) - PHP necesita saber la posición actual en la matriz original a pesar de que en realidad está iterando sobre una copia.Respuestas:
foreach
admite iteración sobre tres tipos diferentes de valores:Traversable
objetosA continuación, intentaré explicar con precisión cómo funciona la iteración en diferentes casos. Con mucho, el caso más simple son los
Traversable
objetos, ya que para estosforeach
es esencialmente solo el azúcar de sintaxis para el código a lo largo de estas líneas:Para las clases internas, las llamadas a métodos reales se evitan mediante el uso de una API interna que esencialmente solo refleja la
Iterator
interfaz en el nivel C.La iteración de matrices y objetos simples es significativamente más complicada. En primer lugar, debe tenerse en cuenta que en PHP las "matrices" son diccionarios realmente ordenados y se recorrerán de acuerdo con este orden (que coincide con el orden de inserción siempre y cuando no haya utilizado algo así
sort
). Esto se opone a iterar por el orden natural de las teclas (cómo funcionan a menudo las listas en otros idiomas) o no tener un orden definido en absoluto (cómo funcionan a menudo los diccionarios en otros idiomas).Lo mismo también se aplica a los objetos, ya que las propiedades de los objetos se pueden ver como otros nombres de propiedades de mapeo de diccionario (ordenados) a sus valores, más un poco de manejo de visibilidad. En la mayoría de los casos, las propiedades del objeto no se almacenan de esta manera bastante ineficiente. Sin embargo, si comienza a iterar sobre un objeto, la representación empaquetada que se usa normalmente se convertirá en un diccionario real. En ese punto, la iteración de objetos simples se vuelve muy similar a la iteración de matrices (por lo que no estoy discutiendo mucho sobre la iteración de objetos simples aquí).
Hasta aquí todo bien. Iterar sobre un diccionario no puede ser demasiado difícil, ¿verdad? Los problemas comienzan cuando te das cuenta de que una matriz / objeto puede cambiar durante la iteración. Hay varias formas en que esto puede suceder:
foreach ($arr as &$v)
entonces$arr
se convierte en una referencia y puede cambiarlo durante la iteración.$ref =& $arr; foreach ($ref as $v)
El problema con permitir modificaciones durante la iteración es el caso en el que se elimina el elemento en el que se encuentra actualmente. Supongamos que usa un puntero para realizar un seguimiento del elemento de matriz en el que se encuentra actualmente. Si este elemento ahora está liberado, le queda un puntero colgante (que generalmente resulta en una falla predeterminada).
Hay diferentes formas de resolver este problema. PHP 5 y PHP 7 difieren significativamente en este aspecto y describiré ambos comportamientos a continuación. El resumen es que el enfoque de PHP 5 fue bastante tonto y condujo a todo tipo de problemas extraños, mientras que el enfoque más complicado de PHP 7 da como resultado un comportamiento más predecible y consistente.
Como último aspecto preliminar, debe tenerse en cuenta que PHP utiliza el recuento de referencias y la copia en escritura para administrar la memoria. Esto significa que si "copia" un valor, en realidad solo reutiliza el valor anterior e incrementa su recuento de referencia (recuento). Solo una vez que realice algún tipo de modificación, se realizará una copia real (llamada "duplicación"). Vea Le están mintiendo para una introducción más extensa sobre este tema.
PHP 5
Puntero de matriz interna y HashPointer
Las matrices en PHP 5 tienen un "puntero de matriz interno" (IAP) dedicado, que admite modificaciones correctamente: cada vez que se elimina un elemento, se comprobará si el IAP apunta a este elemento. Si lo hace, se avanza al siguiente elemento en su lugar.
Si bien
foreach
hace uso del IAP, hay una complicación adicional: solo hay un IAP, pero una matriz puede ser parte de múltiplesforeach
bucles:Para admitir dos bucles simultáneos con un solo puntero de matriz interno,
foreach
realiza las siguientes travesuras: antes de ejecutar el cuerpo del bucle, realizaráforeach
una copia de seguridad del puntero al elemento actual y su hash en un foreachHashPointer
. Después de que se ejecute el cuerpo del bucle, el IAP se restablecerá en este elemento si aún existe. Sin embargo, si el elemento se ha eliminado, solo lo usaremos donde sea que esté actualmente el IAP. Este esquema funciona principalmente, pero hay un montón de comportamientos extraños que puedes obtener, algunos de los cuales demostraré a continuación.Duplicación de matriz
El IAP es una característica visible de una matriz (expuesta a través de la
current
familia de funciones), ya que tales cambios en el IAP cuentan como modificaciones bajo la semántica de copiar en escritura. Esto, desafortunadamente, significa queforeach
en muchos casos se ve obligado a duplicar la matriz sobre la que está iterando. Las condiciones precisas son:refcount
es 1, la matriz no se comparte y podemos modificarla directamente.Si la matriz no está duplicada (is_ref = 0, refcount = 1), solo
refcount
se incrementará (*). Además, siforeach
se usa por referencia, la matriz (potencialmente duplicada) se convertirá en una referencia.Considere este código como un ejemplo donde se produce la duplicación:
Aquí,
$arr
se duplicará para evitar que los cambios de IAP se$arr
filtren$outerArr
. En términos de las condiciones anteriores, la matriz no es una referencia (is_ref = 0) y se usa en dos lugares (refcount = 2). Este requisito es desafortunado y un artefacto de la implementación subóptima (no hay ninguna preocupación de modificación durante la iteración aquí, por lo que realmente no necesitamos usar el IAP en primer lugar).(*) Incrementar el
refcount
aquí suena inocuo, pero viola la semántica de copia en escritura (COW): Esto significa que vamos a modificar el IAP de una matriz refcount = 2, mientras que COW dicta que las modificaciones solo se pueden realizar en refcount = 1 valores. Esta violación da como resultado un cambio de comportamiento visible para el usuario (mientras que un COW es normalmente transparente) porque el cambio de IAP en la matriz iterada será observable, pero solo hasta la primera modificación no IAP en la matriz. En cambio, las tres opciones "válidas" hubieran sido a) duplicar siempre, b) no incrementenrefcount
y, por lo tanto, permitan que la matriz iterada se modifique arbitrariamente en el bucle o c) no utilicen el IAP (el PHP 7 solución).Orden de avance de posición
Hay un último detalle de implementación que debe tener en cuenta para comprender correctamente los ejemplos de código a continuación. La forma "normal" de recorrer alguna estructura de datos se vería así en pseudocódigo:
Sin embargo
foreach
, al ser un copo de nieve bastante especial, elige hacer las cosas de manera ligeramente diferente:A saber, el puntero de la matriz ya se mueve hacia adelante antes de que se ejecute el cuerpo del bucle. Esto significa que mientras el cuerpo del bucle está trabajando en el elemento
$i
, el IAP ya está en el elemento$i+1
. Esta es la razón por la cual las muestras de código que muestran modificaciones durante la iteración siempre seránunset
el siguiente elemento, en lugar del actual.Ejemplos: sus casos de prueba
Los tres aspectos descritos anteriormente deberían proporcionarle una impresión mayormente completa de las idiosincrasias de la
foreach
implementación y podemos pasar a discutir algunos ejemplos.El comportamiento de sus casos de prueba es simple de explicar en este punto:
En los casos de prueba 1 y 2
$array
comienza con refcount = 1, por lo que no se duplicará porforeach
: Solorefcount
se incrementa el. Cuando el cuerpo del bucle posteriormente modifica la matriz (que tiene refcount = 2 en ese punto), la duplicación ocurrirá en ese punto. Foreach continuará trabajando en una copia no modificada de$array
.En el caso de prueba 3, una vez más la matriz no está duplicada, por
foreach
lo tanto , se modificará el IAP de la$array
variable. Al final de la iteración, el IAP es NULL (lo que significa que la iteración ha terminado), lo queeach
indica al regresarfalse
.En los casos de prueba 4 y 5 tanto
each
yreset
son funciones por referencia. El$array
tiene unarefcount=2
cuando se pasa a ellos, así que tiene que ser duplicado. Como talforeach
, trabajará en una matriz separada nuevamente.Ejemplos: efectos de
current
en foreachUna buena manera de mostrar los diversos comportamientos de duplicación es observar el comportamiento de la
current()
función dentro de unforeach
bucle. Considere este ejemplo:Aquí debe saber que
current()
es una función by-ref (en realidad: prefer-ref), a pesar de que no modifica la matriz. Tiene que ser para jugar bien con todas las otras funciones como lasnext
que son todas por ref. By-referencia de pasada implica que la matriz tiene que ser separado y por lo tanto$array
y elforeach-array
será diferente. La razón que obtienes en2
lugar de1
también se menciona anteriormente:foreach
avanza el puntero de matriz antes de ejecutar el código de usuario, no después. Entonces, aunque el código está en el primer elemento,foreach
ya avanzó el puntero al segundo.Ahora intentemos una pequeña modificación:
Aquí tenemos el caso is_ref = 1, por lo que la matriz no se copia (al igual que arriba). Pero ahora que es una referencia, la matriz ya no tiene que duplicarse al pasar a la función by-ref
current()
. Asícurrent()
yforeach
trabajar en la misma matriz. Sin embargo, todavía ve el comportamiento off-by-one, debido a la forma en queforeach
avanza el puntero.Obtiene el mismo comportamiento cuando realiza la iteración by-ref:
Aquí la parte importante es que foreach creará
$array
un is_ref = 1 cuando se itera por referencia, por lo que básicamente tiene la misma situación que la anterior.Otra pequeña variación, esta vez asignaremos la matriz a otra variable:
Aquí el recuento de la
$array
es 2 cuando se inicia el ciclo, por lo que por una vez tenemos que hacer la duplicación por adelantado. Por lo tanto,$array
y la matriz utilizada por foreach estará completamente separada desde el principio. Es por eso que obtiene la posición del IAP donde estaba antes del bucle (en este caso, estaba en la primera posición).Ejemplos: modificación durante la iteración
Intentar dar cuenta de las modificaciones durante la iteración es donde se originaron todos nuestros problemas foreach, por lo que sirve considerar algunos ejemplos para este caso.
Considere estos bucles anidados sobre la misma matriz (donde se usa la iteración por referencia para asegurarse de que realmente sea la misma):
La parte esperada aquí es que
(1, 2)
falta en la salida porque1
se eliminó el elemento . Lo que probablemente sea inesperado es que el bucle externo se detiene después del primer elemento. ¿Porqué es eso?La razón detrás de esto es el hack de bucle anidado descrito anteriormente: antes de que se ejecute el cuerpo del bucle, la posición IAP actual y el hash se respaldan en a
HashPointer
. Después del cuerpo del bucle, se restaurará, pero solo si el elemento aún existe, de lo contrario, se usa la posición IAP actual (cualquiera que sea). En el ejemplo anterior, este es exactamente el caso: el elemento actual del bucle externo se ha eliminado, por lo que utilizará el IAP, que ya ha sido marcado como terminado por el bucle interno.Otra consecuencia del
HashPointer
mecanismo de copia de seguridad + restauración es que los cambios en el IAP a través dereset()
etc. generalmente no afectanforeach
. Por ejemplo, el siguiente código se ejecuta como sireset()
no estuviera presente:La razón es que, si bien
reset()
modifica temporalmente el IAP, se restaurará al elemento foreach actual después del cuerpo del bucle. Para forzarreset()
un efecto en el bucle, debe eliminar adicionalmente el elemento actual, de modo que el mecanismo de copia de seguridad / restauración falle:Pero, esos ejemplos aún son sanos. La verdadera diversión comienza si recuerda que la
HashPointer
restauración usa un puntero al elemento y su hash para determinar si todavía existe. Pero: los hashes tienen colisiones, y los punteros se pueden reutilizar. Esto significa que, con una elección cuidadosa de las claves de la matriz, podemos hacerforeach
creer que todavía existe un elemento que se ha eliminado, por lo que saltará directamente a él. Un ejemplo:Aquí normalmente deberíamos esperar la salida de
1, 1, 3, 4
acuerdo con las reglas anteriores. Lo que sucede es que'FYFY'
tiene el mismo hash que el elemento eliminado'EzFY'
, y el asignador reutiliza la misma ubicación de memoria para almacenar el elemento. Entonces foreach termina saltando directamente al elemento recién insertado, acortando así el bucle.Sustituyendo la entidad iterada durante el ciclo
Un último caso extraño que me gustaría mencionar, es que PHP le permite sustituir la entidad iterada durante el ciclo. Entonces puede comenzar a iterar en una matriz y luego reemplazarla con otra matriz a mitad de camino. O comience a iterar en una matriz y luego reemplácela con un objeto:
Como puede ver en este caso, PHP solo comenzará a iterar la otra entidad desde el principio una vez que haya ocurrido la sustitución.
PHP 7
Iteradores de tabla hash
Si aún recuerda, el principal problema con la iteración de matriz era cómo manejar la eliminación de elementos a mitad de la iteración. PHP 5 usó un único puntero de matriz interna (IAP) para este propósito, que era algo subóptimo, ya que un puntero de matriz tenía que estirarse para soportar múltiples bucles foreach simultáneos e interacción con
reset()
etc., además de eso.PHP 7 utiliza un enfoque diferente, es decir, admite la creación de una cantidad arbitraria de iteradores de tabla hash seguros y externos. Estos iteradores deben registrarse en la matriz, a partir de ese punto tienen la misma semántica que el IAP: si se elimina un elemento de la matriz, todos los iteradores de tabla hash que apuntan a ese elemento avanzarán al siguiente elemento.
Esto significa que
foreach
ya no se vayan a utilizar el IAP en absoluto . Elforeach
ciclo no tendrá ningún efecto en los resultados decurrent()
etc. y su propio comportamiento nunca estará influenciado por funciones comoreset()
etc.Duplicación de matriz
Otro cambio importante entre PHP 5 y PHP 7 se relaciona con la duplicación de matrices. Ahora que el IAP ya no se usa, la iteración de matriz por valor solo hará un
refcount
incremento (en lugar de duplicar la matriz) en todos los casos. Si la matriz se modifica durante elforeach
ciclo, en ese momento se producirá una duplicación (de acuerdo con la copia en escritura) yforeach
seguirá trabajando en la matriz anterior.En la mayoría de los casos, este cambio es transparente y no tiene otro efecto que un mejor rendimiento. Sin embargo, hay una ocasión en la que da como resultado un comportamiento diferente, a saber, el caso en el que la matriz era una referencia previa:
Anteriormente, la iteración por valor de las matrices de referencia era casos especiales. En este caso, no se produjo duplicación, por lo que todas las modificaciones de la matriz durante la iteración se reflejarían en el bucle. En PHP 7, este caso especial desapareció: una iteración por valor de una matriz siempre seguirá trabajando en los elementos originales, sin tener en cuenta las modificaciones durante el ciclo.
Esto, por supuesto, no se aplica a la iteración por referencia. Si itera por referencia, todas las modificaciones se reflejarán en el bucle. Curiosamente, lo mismo es cierto para la iteración por valor de objetos simples:
Esto refleja la semántica de los objetos (por ejemplo, se comportan como referencias incluso en contextos de valor).
Ejemplos
Consideremos algunos ejemplos, comenzando con sus casos de prueba:
Los casos de prueba 1 y 2 conservan el mismo resultado: la iteración de matriz de valores siempre funciona en los elementos originales. (En este caso, el
refcounting
comportamiento uniforme y de duplicación es exactamente el mismo entre PHP 5 y PHP 7).Cambios en el caso de prueba 3:
Foreach
ya no usa el IAP, poreach()
lo que no se ve afectado por el bucle. Tendrá la misma salida antes y después.Los casos de prueba 4 y 5 permanecen igual:
each()
yreset()
duplicarán la matriz antes de cambiar el IAP, mientras queforeach
todavía usa la matriz original. (No es que el cambio de IAP hubiera importado, incluso si se hubiera compartido la matriz).El segundo conjunto de ejemplos estaba relacionado con el comportamiento de
current()
diferentesreference/refcounting
configuraciones. Esto ya no tiene sentido, ya quecurrent()
el ciclo no lo afecta por completo, por lo que su valor de retorno siempre permanece igual.Sin embargo, obtenemos algunos cambios interesantes cuando consideramos modificaciones durante la iteración. Espero que encuentres el nuevo comportamiento más sano. El primer ejemplo:
Como puede ver, el bucle externo ya no se cancela después de la primera iteración. La razón es que ambos bucles ahora tienen iteradores de tabla hash completamente separados, y ya no hay contaminación cruzada de ambos bucles a través de un IAP compartido.
Otro caso de borde extraño que se soluciona ahora, es el efecto extraño que obtienes al eliminar y agregar elementos que tienen el mismo hash:
Anteriormente, el mecanismo de restauración de HashPointer saltaba directamente al nuevo elemento porque "parecía" que era lo mismo que el elemento eliminado (debido al hash y al puntero colisionantes). Como ya no confiamos en el elemento hash para nada, esto ya no es un problema.
fuente
$foo = $array
antes del ciclo;)Bucket
s son parte de una lista doblemente vinculada para colisiones de hash y también parte de una lista doblemente vinculada para el orden;)iterate($outerArr);
y no eniterate($arr);
alguna parte.En el ejemplo 3 no modificas la matriz. En todos los demás ejemplos, modifica el contenido o el puntero interno de la matriz. Esto es importante cuando se trata de matrices PHP debido a la semántica del operador de asignación.
El operador de asignación para las matrices en PHP funciona más como un clon perezoso. Asignar una variable a otra que contenga una matriz clonará la matriz, a diferencia de la mayoría de los idiomas. Sin embargo, la clonación real no se realizará a menos que sea necesaria. Esto significa que el clon tendrá lugar solo cuando cualquiera de las variables se modifique (copiar en escritura).
Aquí hay un ejemplo:
Volviendo a sus casos de prueba, puede imaginar fácilmente que
foreach
crea algún tipo de iterador con una referencia a la matriz. Esta referencia funciona exactamente como la variable$b
en mi ejemplo. Sin embargo, el iterador junto con la referencia viven solo durante el ciclo y luego, ambos se descartan. Ahora puede ver que, en todos los casos excepto 3, la matriz se modifica durante el ciclo, mientras esta referencia adicional está activa. Esto desencadena un clon, y eso explica lo que está sucediendo aquí.Aquí hay un excelente artículo para otro efecto secundario de este comportamiento de copia en escritura: El Operador PHP Ternario: ¿Rápido o no?
fuente
each()
al final del primer caso de prueba, donde vemos que el puntero de la matriz de la matriz original apunta al segundo elemento, ya que la matriz se modificó durante La primera iteración. Esto también parece demostrar queforeach
mueve el puntero de la matriz antes de ejecutar el bloque de código del bucle, lo que no esperaba; hubiera pensado que haría esto al final. Muchas gracias, esto me aclara muy bien.Algunos puntos a tener en cuenta al trabajar con
foreach()
:a)
foreach
funciona en la copia prospectiva de la matriz original. Significa queforeach()
tendrá almacenamiento de datos COMPARTIDO hasta o a menosprospected copy
que no se haya creado para cada Notas / Comentarios del usuario .b) ¿Qué desencadena una copia prospectada ? Se crea una copia prospectada en función de la política de
copy-on-write
, es decir, cada vez queforeach()
se cambia una matriz que se pasa , se crea un clon de la matriz original.c) La matriz original y el
foreach()
iterador tendránDISTINCT SENTINEL VARIABLES
, es decir, uno para la matriz original y otro paraforeach
; ver el código de prueba a continuación. SPL , Iteradores e Iterador de matriz .Pregunta de desbordamiento de pila ¿ Cómo asegurarse de que el valor se restablezca en un bucle 'foreach' en PHP? aborda los casos (3,4,5) de su pregunta.
El siguiente ejemplo muestra que cada () y reset () NO afectan las
SENTINEL
variables(for example, the current index variable)
delforeach()
iterador.Salida:
fuente
foreach
opera en una copia potencial de la matriz, pero no realiza la copia real a menos que sea necesaria.foreach
está copiando la matriz el 100% del tiempo. Estoy ansioso por saber Gracias por sus comentariosfor
oforeach
. No verá ninguna diferencia significativa entre los dos, porque no se realiza una copia real.SHARED data storage
reservado hasta o a menoscopy-on-write
, pero (de mi fragmento de código) es evidente que siempre habrá DOS conjuntos deSENTINEL variables
uno para eloriginal array
otroforeach
. Gracias, eso tiene sentidoNOTA PARA PHP 7
Para actualizar esta respuesta, ya que ha ganado algo de popularidad: esta respuesta ya no se aplica a partir de PHP 7. Como se explica en " Cambios incompatibles con versiones anteriores ", en PHP 7 foreach funciona en la copia de la matriz, por lo que cualquier cambio en la matriz en sí no se reflejan en el bucle foreach. Más detalles en el enlace.
Explicación (cita de php.net ):
Entonces, en su primer ejemplo, solo tiene un elemento en la matriz, y cuando se mueve el puntero, el siguiente elemento no existe, por lo tanto, después de agregar un nuevo elemento, cada extremo termina porque ya "decidió" que es el último elemento.
En su segundo ejemplo, comienza con dos elementos, y el bucle foreach no está en el último elemento, por lo que evalúa la matriz en la próxima iteración y, por lo tanto, se da cuenta de que hay un nuevo elemento en la matriz.
Creo que todo esto es consecuencia de En cada iteración, parte de la explicación en la documentación, lo que probablemente significa que
foreach
hace toda la lógica antes de llamar al código{}
.Caso de prueba
Si ejecuta esto:
Obtendrá esta salida:
Lo que significa que aceptó la modificación y la realizó porque se modificó "a tiempo". Pero si haces esto:
Conseguirás:
Lo que significa que la matriz se modificó, pero dado que la modificamos cuando
foreach
ya estaba en el último elemento de la matriz, "decidió" no hacer más bucles, y aunque agregamos un nuevo elemento, lo agregamos "demasiado tarde" y no fue atravesado.La explicación detallada se puede leer en ¿Cómo funciona realmente PHP 'foreach'? lo que explica lo interno detrás de este comportamiento.
fuente
Según la documentación proporcionada por el manual de PHP.
Entonces, según su primer ejemplo:
$array
tener solo un elemento, por lo que según la ejecución foreach, 1 asigno$v
y no tiene ningún otro elemento para mover el punteroPero en tu segundo ejemplo:
$array
tiene dos elementos, por lo que ahora $ array evalúa los índices cero y mueve el puntero por uno. Para la primera iteración del bucle, se agrega$array['baz']=3;
como pase por referencia.fuente
Gran pregunta, porque muchos desarrolladores, incluso los experimentados, están confundidos por la forma en que PHP maneja las matrices en bucles foreach. En el bucle foreach estándar, PHP hace una copia de la matriz que se usa en el bucle. La copia se descarta inmediatamente después de que finaliza el bucle. Esto es transparente en la operación de un bucle foreach simple. Por ejemplo:
Esto produce:
Por lo tanto, se crea la copia pero el desarrollador no se da cuenta, porque la matriz original no está referenciada dentro del ciclo o después de que finaliza el ciclo. Sin embargo, cuando intenta modificar los elementos en un bucle, descubre que no se modifican cuando termina:
Esto produce:
Los cambios del original no pueden ser avisos, en realidad no hay cambios del original, a pesar de que claramente asignó un valor a $ item. Esto se debe a que está operando en $ item tal como aparece en la copia de $ set en la que se está trabajando. Puede anular esto tomando $ item por referencia, así:
Esto produce:
Por lo tanto, es evidente y observable que, cuando $ item se opera por referencia, los cambios realizados en $ item se realizan a los miembros del conjunto $ original. El uso de $ item por referencia también evita que PHP cree la copia de la matriz. Para probar esto, primero mostraremos un script rápido que demuestra la copia:
Esto produce:
Como se muestra en el ejemplo, PHP copió $ set y lo usó para recorrer, pero cuando se usó $ set dentro del loop, PHP agregó las variables a la matriz original, no a la matriz copiada. Básicamente, PHP solo está utilizando la matriz copiada para la ejecución del bucle y la asignación de $ item. Debido a esto, el ciclo anterior solo se ejecuta 3 veces, y cada vez que agrega otro valor al final del conjunto $ original, dejando el conjunto $ original con 6 elementos, pero nunca ingresando un ciclo infinito.
Sin embargo, ¿qué pasaría si hubiéramos usado $ item por referencia, como mencioné antes? Un solo carácter agregado a la prueba anterior:
Resultados en un bucle infinito. Tenga en cuenta que en realidad es un bucle infinito, tendrá que eliminar el script usted mismo o esperar a que su sistema operativo se quede sin memoria. Agregué la siguiente línea a mi script para que PHP se quedara sin memoria muy rápidamente, le sugiero que haga lo mismo si va a ejecutar estas pruebas de bucle infinito:
Entonces, en este ejemplo anterior con el bucle infinito, vemos la razón por la cual PHP se escribió para crear una copia de la matriz para realizar un bucle. Cuando se crea una copia y se usa solo por la estructura de la construcción del bucle, la matriz permanece estática durante la ejecución del bucle, por lo que nunca se encontrará con problemas.
fuente
El bucle foreach de PHP se puede usar con
Indexed arrays
,Associative arrays
yObject public variables
.En foreach loop, lo primero que hace php es crear una copia de la matriz que se debe repetir. PHP luego itera sobre esta nueva
copy
matriz en lugar de la original. Esto se demuestra en el siguiente ejemplo:Además de esto, php también permite su uso
iterated values as a reference to the original array value
. Esto se demuestra a continuación:Nota: No permite
original array indexes
ser utilizado comoreferences
.Fuente: http://dwellupper.io/post/47/understanding-php-foreach-loop-with-examples
fuente
Object public variables
está mal o, en el mejor de los casos, es engañoso. No puede usar un objeto en una matriz sin la interfaz correcta (por ejemplo, Traversible) y cuando lo haceforeach((array)$obj ...
, de hecho está trabajando con una matriz simple, ya no es un objeto.