w3schools - recorrer array asociativo php




¿Cómo funciona PHP 'foreach' realmente funciona? (5)

Permítame prefijar esto diciendo que sé lo que es foreach , lo que 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 hace un bucle de una matriz con foreach ".

Durante mucho tiempo asumí que foreach trabajaba con la propia matriz. Luego encontré muchas referencias al hecho de que funciona con una copia de la matriz, y desde entonces asumí que este es el final de la historia. Pero recientemente tuve una discusión sobre el tema, y ​​después de una pequeña experimentación descubrí que esto no era en realidad un 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);

Caso de prueba 1 :

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 de origen; de lo contrario, el bucle continuaría para siempre, ya que estamos presionando constantemente los elementos en la matriz durante el bucle. Pero solo para estar seguro de que este es el caso:

Caso de prueba 2 :

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 de origen durante el bucle, de lo contrario veríamos los valores modificados durante el bucle. Pero...

Si miramos en el manual , nos encontramos con esta declaración:

Cuando foreach comienza a ejecutarse por primera vez, el puntero de la matriz interna se restablece automáticamente al primer elemento de la matriz.

Correcto ... esto parece sugerir que foreach basa en el puntero de matriz de la matriz de origen. Pero acabamos de demostrar que no estamos trabajando con la matriz de origen , ¿verdad? Bueno, no del todo.

Caso de prueba 3 :

// 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 de que no estamos trabajando directamente con la matriz de origen, estamos trabajando directamente con el puntero de la matriz de origen; el hecho de que el puntero esté al final de la matriz al final del bucle lo muestra. Excepto que esto no puede ser cierto: si lo fuera, entonces el caso de prueba 1 se interrumpiría para siempre.

El manual de PHP también establece:

Como foreach se basa en el puntero de la matriz interna, cambiarlo dentro del bucle puede provocar un comportamiento inesperado.

Bueno, averigüemos qué es ese "comportamiento inesperado" (técnicamente, cualquier comportamiento es inesperado ya que ya no sé qué esperar).

Caso de prueba 4 :

foreach ($array as $key => $item) {
  echo "$item\n";
  each($array);
}

/* Output: 1 2 3 4 5 */

Caso de prueba 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 correcta simplemente mirando el código fuente de PHP, agradecería que alguien lo tradujera al inglés para mí.

Me parece que foreach trabaja con una copia de la matriz, pero establece el puntero de matriz 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 ajusten el puntero de la matriz ( each() , reset() y otros) durante un foreach podría afectar el resultado del bucle?

Algunos puntos a tener en cuenta al trabajar con foreach() :

a) foreach trabaja en la copia prospectiva de la matriz original. Significa que foreach () tendrá un almacenamiento de datos COMPARTIDO hasta que, a menos que no se cree una prospected copy manual .

b) ¿Qué desencadena una copia prospectada ? La copia prospectiva se crea en función de la política de copy-on-write , es decir, cada vez que se cambia una matriz pasada a foreach (), se crea un clon de la matriz original.

c) La matriz original y el iterador foreach () tendrán DISTINCT SENTINEL VARIABLES , es decir, una para la matriz original y otra para foreach; Consulte el código de prueba a continuación. SPL , Iterators e Iterador de Array .

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 tu pregunta.

El siguiente ejemplo muestra que cada () y reset () NO afectan SENTINEL variables SENTINEL (for example, the current index variable) del iterador foreach ().

$array = array(1, 2, 3, 4, 5);

list($key2, $val2) = each($array);
echo "each() Original (outside): $key2 => $val2<br/>";

foreach($array as $key => $val){
    echo "foreach: $key => $val<br/>";

    list($key2,$val2) = each($array);
    echo "each() Original(inside): $key2 => $val2<br/>";

    echo "--------Iteration--------<br/>";
    if ($key == 3){
        echo "Resetting original array pointer<br/>";
        reset($array);
    }
}

list($key2, $val2) = each($array);
echo "each() Original (outside): $key2 => $val2<br/>";

Salida:

each() Original (outside): 0 => 1
foreach: 0 => 1
each() Original(inside): 1 => 2
--------Iteration--------
foreach: 1 => 2
each() Original(inside): 2 => 3
--------Iteration--------
foreach: 2 => 3
each() Original(inside): 3 => 4
--------Iteration--------
foreach: 3 => 4
each() Original(inside): 4 => 5
--------Iteration--------
Resetting original array pointer
foreach: 4 => 5
each() Original(inside): 0=>1
--------Iteration--------
each() Original (outside): 1 => 2

En el ejemplo 3 no modificas la matriz. En todos los demás ejemplos, modifica el contenido o el puntero de matriz interno. 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. La asignación de una variable a otra que contiene 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 solo tendrá lugar cuando se modifique cualquiera de las variables (copia en escritura).

Aquí hay un ejemplo:

$a = array(1,2,3);
$b = $a;  // This is lazy cloning of $a. For the time
          // being $a and $b point to the same internal
          // data structure.

$a[] = 3; // Here $a changes, which triggers the actual
          // cloning. From now on, $a and $b are two
          // different data structures. The same would
          // happen if there were a change in $b.

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 en vivo solo durante el bucle y luego, ambos se descartan. Ahora puede ver que, en todos los casos, excepto 3, la matriz se modifica durante el bucle, mientras esta referencia adicional está activa. ¡Esto activa un clon, y eso explica lo que está pasando aquí!

Este es un excelente artículo sobre otro efecto secundario de este comportamiento de copia en escritura: El operador PHP ternario: ¿Rápido o no?


NOTA PARA PHP 7

Para actualizar esta respuesta, ya que ha ganado cierta 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 ):

El primer formulario recorre la matriz dada por array_expression. En cada iteración, el valor del elemento actual se asigna a $ valor y el puntero de la matriz interna avanza en uno (de modo que en la siguiente iteración, verá el elemento siguiente).

Por lo tanto, 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, foreach termina porque ya "decidió" que era el último elemento.

En su segundo ejemplo, comienza con dos elementos, y foreach loop no está en el último elemento, por lo que evalúa la matriz en la siguiente 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 foreachhace toda la lógica antes de llamar al código {}.

Caso de prueba

Si ejecuta esto:

<?
    $array = Array(
        'foo' => 1,
        'bar' => 2
    );
    foreach($array as $k=>&$v) {
        $array['baz']=3;
        echo $v." ";
    }
    print_r($array);
?>

Obtendrá esta salida:

1 2 3 Array
(
    [foo] => 1
    [bar] => 2
    [baz] => 3
)

Lo que significa que aceptó la modificación y la revisó porque se modificó "a tiempo". Pero si haces esto:

<?
    $array = Array(
        'foo' => 1,
        'bar' => 2
    );
    foreach($array as $k=>&$v) {
        if ($k=='bar') {
            $array['baz']=3;
        }
        echo $v." ";
    }
    print_r($array);
?>

Conseguirás:

1 2 Array
(
    [foo] => 1
    [bar] => 2
    [baz] => 3
)

Lo que significa que la matriz se modificó, pero como la modificamos cuando foreachya estaba en el último elemento de la matriz, "decidió" no hacer un bucle más, y aunque agregamos un elemento nuevo, lo agregamos "demasiado tarde" y no fue en bucle a través de

La explicación detallada se puede leer en ¿Cómo funciona PHP 'foreach' realmente? lo que explica los aspectos internos detrás de este comportamiento.


Según la documentación proporcionada por el manual de PHP.

En cada iteración, el valor del elemento actual se asigna a $ vy el
puntero de la matriz interna avanza en uno (de modo que en la siguiente iteración, verá el siguiente elemento).

Así como por su primer ejemplo:

$array = ['foo'=>1];
foreach($array as $k=>&$v)
{
   $array['bar']=2;
   echo($v);
}

$arraytiene solo un elemento, por lo que según la ejecución de foreach, asigno a $vy no tiene ningún otro elemento para mover el puntero

Pero en tu segundo ejemplo:

$array = ['foo'=>1, 'bar'=>2];
foreach($array as $k=>&$v)
{
   $array['baz']=3;
   echo($v);
}

$arraytiene dos elementos, por lo que ahora $ array evalúa los índices cero y mueve el puntero en uno. Para la primera iteración del bucle, se agrega $array['baz']=3;como pase por referencia.


PHP foreach loop se puede usar con Indexed arrays, Associative arraysy Object public variables.

En el bucle foreach, lo primero que hace php es que crea una copia de la matriz que se va a iterar. PHP luego itera sobre este nuevo copyde la matriz en lugar de la original. Esto se demuestra en el siguiente ejemplo:

<?php
$numbers = [1,2,3,4,5,6,7,8,9]; # initial values for our array
echo '<pre>', print_r($numbers, true), '</pre>', '<hr />';
foreach($numbers as $index => $number){
    $numbers[$index] = $number + 1; # this is making changes to the origial array
    echo 'Inside of the array = ', $index, ': ', $number, '<br />'; # showing data from the copied array
}
echo '<hr />', '<pre>', print_r($numbers, true), '</pre>'; # shows the original values (also includes the newly added values).

Además de esto, php permite usar iterated values as a reference to the original array valuetambién. Esto se demuestra a continuación:

<?php
$numbers = [1,2,3,4,5,6,7,8,9];
echo '<pre>', print_r($numbers, true), '</pre>';
foreach($numbers as $index => &$number){
    ++$number; # we are incrementing the original value
    echo 'Inside of the array = ', $index, ': ', $number, '<br />'; # this is showing the original value
}
echo '<hr />';
echo '<pre>', print_r($numbers, true), '</pre>'; # we are again showing the original value

Nota: No permite original array indexesser utilizado como references.

Fuente: http://dwellupper.io/post/47/understanding-php-foreach-loop-with-examples





php-internals