foreach javascript - ¿Cómo funciona realmente el 'foreach' de PHP?

invalid argument supplied for foreach() / php / loops / foreach / iteration / php-internals

Permítanme anteponer esto diciendo que sé qué es foreach , qué hace y cómo usarlo. Esta pregunta se refiere a cómo funciona debajo del capó, y no quiero ninguna respuesta como "así es como se hace un bucle en una matriz con foreach ".

Permítanme mostrar lo que quiero decir.Para los siguientes casos de prueba,trabajaremos con el siguiente array:

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

Caso de prueba 1 :

foreach ($array as $item) {
  echo "$item\n";
  $array[] = $item;
}
print_r($array);

/ * Salida en bucle: 1 2 3 4 5 $ matriz tras bucle: 1 2 3 4 5 1 2 3 4 5 * /

Caso de prueba 2 :

foreach ($array as $key => $item) {
  $array[$key + 1] = $item + 2;
  echo "$item\n";
}

print_r($array);

/ * Salida en bucle: 1 2 3 4 5 $ matriz tras bucle: 1 3 4 5 6 7 * /

Caso de prueba 3 :

// Mueva el puntero de la matriz en uno para asegurarse de que no afecte al bucle
var_dump(each($array));

foreach ($array as $item) {
  echo "$item\n";
}

var_dump(each($array));

/ * Matriz de salida (4) {[1] => int (1) ["valor"] => int (1) [0] => int (0) ["clave"] => int (0)} 1 2 3 4 5 bool (falso) * /

Caso de prueba 4 :

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

/ * Salida: 1 2 3 4 5 * /

Caso de prueba 5 :

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

/ * Salida: 1 2 3 4 5 * /

sergiol



Answer #1
foreach ($it as $k => $v) { /* ... */ }

/ * se traduce en: * /

if ($it instanceof IteratorAggregate) {
    $it = $it->getIterator();
}
for ($it->rewind(); $it->valid(); $it->next()) {
    $v = $it->current();
    $k = $it->key();
    /* ... */
}
// Usando la iteración por referencia aquí para asegurarse de que sea realmente
// la misma matriz en ambos bucles y no una copia
foreach ($arr as &$v1) {
    foreach ($arr as &$v) {
        // ...
    }
}

Considere este código como un ejemplo en el que se produce una duplicación:

function iterate($arr) {
    foreach ($arr as $v) {}
}

$outerArr = [0, 1, 2, 3, 4];
iterate($outerArr);

Hay un último detalle de implementación que debes tener en cuenta para entender correctamente los ejemplos de código de abajo.La forma "normal" de hacer un bucle a través de una estructura de datos sería algo así en pseudocódigo:

reset(arr);
while (get_current_data(arr, &data) == SUCCESS) {
    code();
    move_forward(arr);
}
reset(arr);
while (get_current_data(arr, &data) == SUCCESS) {
    move_forward(arr);
    code();
}

Los tres aspectos descritos anteriormente deberían proporcionarle una impresión mayoritariamente completa de las idiosincrasias de la implementación de foreach y podemos pasar a discutir algunos ejemplos.

foreach ($array as $val) {
    var_dump(current($array));
}
/ * Salida: 2 2 2 2 2 * /

Ahora vamos a probar una pequeña modificación:

$ref = &$array;
foreach ($array as $val) {
    var_dump(current($array));
}
/ * Salida: 2 3 4 5 falso * /

Se obtiene el mismo comportamiento al hacer la iteración by-ref:

foreach ($array as &$val) {
    var_dump(current($array));
}
/ * Salida: 2 3 4 5 falso * /

Otra pequeña variación,esta vez asignaremos el array a otra variable:

$foo = $array;
foreach ($array as $val) {
    var_dump(current($array));
}
/ * Salida: 1 1 1 1 1 * /

Considere estos bucles anidados sobre el mismo array (donde se utiliza la iteración by-ref para asegurarse de que realmente es el mismo):

foreach ($array as &$v1) {
    foreach ($array as &$v2) {
        if ($v1 == 1 && $v2 == 1) {
            unset($array[1]);
        }
        echo "($v1, $v2)\n";
    }
}

// Salida: (1, 1) (1, 3) (1, 4) (1, 5)
$array = [1, 2, 3, 4, 5];
foreach ($array as &$value) {
    var_dump($value);
    reset($array);
}
// salida: 1, 2, 3, 4, 5
$array = [1, 2, 3, 4, 5];
$ref =& $array;
foreach ($array as $value) {
    var_dump($value);
    unset($array[1]);
    reset($array);
}
// salida: 1, 1, 3, 4, 5
$array = ['EzEz' => 1, 'EzFY' => 2, 'FYEz' => 3];
$ref =& $array;
foreach ($array as $value) {
    unset($array['EzFY']);
    $array['FYFY'] = 4;
    reset($array);
    var_dump($value);
}
// salida: 1, 4

Un último caso extraño que me gustaría mencionar,es que PHP te permite sustituir la entidad iterada durante el bucle.Así que puedes empezar a iterar sobre un array y luego sustituirlo por otro array a mitad de camino.O empezar a iterar sobre un array y luego sustituirlo por un objeto:

$arr = [1, 2, 3, 4, 5];
$obj = (object) [6, 7, 8, 9, 10];

$ref =& $arr;
foreach ($ref as $val) {
    echo "$val\n";
    if ($val == 3) {
        $ref = $obj;
    }
}
/ * Salida: 1 2 3 6 7 8 9 10 * /

En la mayoría de los casos,este cambio es transparente y no tiene más efecto que un mejor rendimiento.Sin embargo,hay una ocasión en la que da lugar a un comportamiento diferente,a saber,el caso en el que el array era una referencia de antemano:

$array = [1, 2, 3, 4, 5];
$ref = &$array;
foreach ($array as $val) {
    var_dump($val);
    $array[2] = 0;
}
/ * Salida anterior: 1, 2, 0, 4, 5 * /
/ * Nueva salida: 1, 2, 3, 4, 5 * /

Esto,por supuesto,no se aplica a la iteración por referencia.Si se itera por referencia,todas las modificaciones se reflejarán en el bucle.Curiosamente,lo mismo ocurre con la iteración por valor de los objetos planos:

$obj = new stdClass;
$obj->foo = 1;
$obj->bar = 2;
foreach ($obj as $val) {
    var_dump($val);
    $obj->bar = 42;
}
/ * Salida antigua y nueva: 1, 42 * /

Sin embargo,obtenemos algunos cambios interesantes al considerar las modificaciones durante la iteración.Espero que encuentres el nuevo comportamiento más sano.El primer ejemplo:

$array = [1, 2, 3, 4, 5];
foreach ($array as &$v1) {
    foreach ($array as &$v2) {
        if ($v1 == 1 && $v2 == 1) {
            unset($array[1]);
        }
        echo "($v1, $v2)\n";
    }
}

// Salida anterior: (1, 1) (1, 3) (1, 4) (1, 5)
// Nueva salida: (1, 1) (1, 3) (1, 4) (1, 5)
//             (3, 1) (3, 3) (3, 4) (3, 5)
//             (4, 1) (4, 3) (4, 4) (4, 5)
//             (5, 1) (5, 3) (5, 4) (5, 5)

Otro caso extraño que se ha solucionado es el extraño efecto que se produce al eliminar y añadir elementos que tienen el mismo hash:

$array = ['EzEz' => 1, 'EzFY' => 2, 'FYEz' => 3];
foreach ($array as &$value) {
    unset($array['EzFY']);
    $array['FYFY'] = 4;
    var_dump($value);
}
// Salida anterior: 1, 4
// Nueva salida: 1, 3, 4