Wie funktioniert PHP 'foreach' eigentlich?

2018

Lassen Sie mich dies voranstellen, indem Sie sagen, dass ich weiß, was foreachist, tut und wie ich es verwenden soll. Diese Frage betrifft die Funktionsweise unter der Motorhaube, und ich möchte keine Antworten im Sinne von "So schleifen Sie ein Array mit foreach".


Lange habe ich angenommen, dass das foreachmit dem Array selbst funktioniert. Dann fand ich viele Hinweise darauf, dass es mit einer Kopie des Arrays funktioniert , und ich habe seitdem angenommen, dass dies das Ende der Geschichte ist. Aber ich bin kürzlich in eine Diskussion über diese Angelegenheit geraten und habe nach ein wenig Experimentieren festgestellt, dass dies tatsächlich nicht 100% wahr ist.

Lassen Sie mich zeigen, was ich meine. Für die folgenden Testfälle arbeiten wir mit dem folgenden Array:

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

Testfall 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 */

Dies zeigt deutlich, dass wir nicht direkt mit dem Quell-Array arbeiten - andernfalls würde die Schleife für immer fortgesetzt, da wir während der Schleife ständig Elemente auf das Array schieben. Aber nur um sicherzugehen, dass dies der Fall ist:

Testfall 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 */

Dies stützt unsere anfängliche Schlussfolgerung, dass wir während der Schleife mit einer Kopie des Quellarrays arbeiten, andernfalls würden wir die geänderten Werte während der Schleife sehen. Aber...

Wenn wir in das Handbuch schauen , finden wir diese Aussage:

Wenn foreach zum ersten Mal ausgeführt wird, wird der interne Array-Zeiger automatisch auf das erste Element des Arrays zurückgesetzt.

Richtig ... dies scheint darauf foreachhinzudeuten , dass dies auf dem Array-Zeiger des Quell-Arrays beruht. Aber wir haben gerade bewiesen, dass wir nicht mit dem Quell-Array arbeiten , oder? Nun, nicht ganz.

Testfall 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)
*/

Trotz der Tatsache, dass wir nicht direkt mit dem Quellarray arbeiten, arbeiten wir direkt mit dem Quellarrayzeiger - die Tatsache, dass sich der Zeiger am Ende des Arrays am Ende der Schleife befindet, zeigt dies. Außer dies kann nicht wahr sein - wenn es so wäre, würde Testfall 1 für immer eine Schleife bilden.

Das PHP-Handbuch besagt außerdem:

Da foreach darauf angewiesen ist, dass der interne Array-Zeiger innerhalb der Schleife geändert wird, kann dies zu unerwartetem Verhalten führen.

Lassen Sie uns herausfinden, was dieses "unerwartete Verhalten" ist (technisch gesehen ist jedes Verhalten unerwartet, da ich nicht mehr weiß, was mich erwartet).

Testfall 4 :

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

/* Output: 1 2 3 4 5 */

Testfall 5 :

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

/* Output: 1 2 3 4 5 */

... nichts so Unerwartetes, tatsächlich scheint es die "Kopie der Quelle" -Theorie zu unterstützen.


Die Frage

Was geht hier vor sich? Mein C-Fu ist nicht gut genug für mich, um eine richtige Schlussfolgerung zu ziehen, indem ich einfach den PHP-Quellcode betrachte. Ich würde es begrüßen, wenn jemand ihn für mich ins Englische übersetzen könnte.

Es scheint mir, dass dies foreachmit einer Kopie des Arrays funktioniert , aber den Array-Zeiger des Quell-Arrays nach der Schleife auf das Ende des Arrays setzt.

  • Ist das richtig und die ganze Geschichte?
  • Wenn nicht, was macht es dann wirklich?
  • Gibt es eine Situation , in den Funktionen, die den Array Zeiger einstellen ( each(), reset()et al.) Während einem foreachkonnte das Ergebnis der Schleife beeinflussen?
DaveRandom
quelle
5
@ DaveRandom Es gibt ein PHP-Internals- Tag, zu dem dies wahrscheinlich gehören sollte, aber ich überlasse es Ihnen, zu entscheiden, welches der anderen 5 Tags ersetzt werden soll.
Michael Berkowski
5
sieht aus wie COW, ohne Handle löschen
zb '
149
Zuerst dachte ich »Meine Güte, noch eine Frage für Neulinge. Lesen Sie die Dokumente… hm, eindeutig undefiniertes Verhalten «. Dann habe ich die komplette Frage gelesen und muss sagen: Ich mag es. Sie haben sich einige Mühe gegeben und alle Testfälle geschrieben. ps. Sind Testfall 4 und 5 gleich?
Stricken
21
Nur ein Gedanke darüber, warum es sinnvoll ist, dass der Array-Zeiger berührt wird: PHP muss den internen Array-Zeiger des ursprünglichen Arrays zusammen mit der Kopie zurücksetzen und verschieben, da der Benutzer möglicherweise einen Verweis auf den aktuellen Wert anfordert ( foreach ($array as &$value)) - PHP muss die aktuelle Position im ursprünglichen Array kennen, obwohl es tatsächlich über eine Kopie iteriert.
Niko
4
@ Sean: IMHO, die PHP-Dokumentation ist wirklich ziemlich schlecht darin, die Nuancen der Kernsprachenfunktionen zu beschreiben. Aber das liegt vielleicht daran, dass so viele Ad-hoc-Sonderfälle in die Sprache eingebrannt sind ...
Oliver Charlesworth

Antworten:

1660

foreach unterstützt die Iteration über drei verschiedene Arten von Werten:

Im Folgenden werde ich versuchen, genau zu erklären, wie Iteration in verschiedenen Fällen funktioniert. Bei weitem der einfachste Fall sind TraversableObjekte, da es sich bei diesen foreachim Wesentlichen nur um Syntaxzucker für Code in dieser Richtung handelt:

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

/* translates to: */

if ($it instanceof IteratorAggregate) {
    $it = $it->getIterator();
}
for ($it->rewind(); $it->valid(); $it->next()) {
    $v = $it->current();
    $k = $it->key();
    /* ... */
}

Bei internen Klassen werden tatsächliche Methodenaufrufe vermieden, indem eine interne API verwendet wird, die im Wesentlichen nur die IteratorSchnittstelle auf C-Ebene widerspiegelt.

Die Iteration von Arrays und einfachen Objekten ist erheblich komplizierter. Zuallererst sollte beachtet werden, dass in PHP "Arrays" wirklich geordnete Wörterbücher sind und sie in dieser Reihenfolge durchlaufen werden (die der Einfügereihenfolge entspricht, solange Sie so etwas nicht verwendet haben sort). Dies steht im Gegensatz zu einer Iteration durch die natürliche Reihenfolge der Schlüssel (wie Listen in anderen Sprachen häufig funktionieren) oder einer überhaupt nicht definierten Reihenfolge (wie Wörterbücher in anderen Sprachen häufig funktionieren).

Gleiches gilt auch für Objekte, da die Objekteigenschaften als ein anderes (geordnetes) Wörterbuch angesehen werden können, das Eigenschaftsnamen ihren Werten zuordnet, sowie eine gewisse Sichtbarkeitsbehandlung. In den meisten Fällen werden die Objekteigenschaften nicht auf diese eher ineffiziente Weise gespeichert. Wenn Sie jedoch über ein Objekt iterieren, wird die normalerweise verwendete gepackte Darstellung in ein echtes Wörterbuch konvertiert. An diesem Punkt wird die Iteration von einfachen Objekten der Iteration von Arrays sehr ähnlich (weshalb ich hier nicht viel über die Iteration von einfachen Objekten spreche).

So weit, ist es gut. Das Durchlaufen eines Wörterbuchs kann nicht allzu schwierig sein, oder? Die Probleme beginnen, wenn Sie feststellen, dass sich ein Array / Objekt während der Iteration ändern kann. Dies kann auf verschiedene Arten geschehen:

  • Wenn Sie mit der Referenz iterieren mit foreach ($arr as &$v)dann $arrwird in eine Referenz gedreht und man kann es während der Iteration ändern.
  • In PHP 5 gilt das Gleiche auch, wenn Sie nach Wert iterieren, das Array jedoch zuvor eine Referenz war: $ref =& $arr; foreach ($ref as $v)
  • Objekte haben eine By-Handle-Passing-Semantik, was für die meisten praktischen Zwecke bedeutet, dass sie sich wie Referenzen verhalten. So können Objekte während der Iteration immer geändert werden.

Das Problem beim Zulassen von Änderungen während der Iteration besteht darin, dass das Element, auf dem Sie sich gerade befinden, entfernt wird. Angenommen, Sie verwenden einen Zeiger, um zu verfolgen, an welchem ​​Array-Element Sie sich gerade befinden. Wenn dieses Element jetzt freigegeben wird, bleibt ein baumelnder Zeiger übrig (was normalerweise zu einem Segfault führt).

Es gibt verschiedene Möglichkeiten, dieses Problem zu lösen. PHP 5 und PHP 7 unterscheiden sich in dieser Hinsicht erheblich und ich werde beide Verhaltensweisen im Folgenden beschreiben. Die Zusammenfassung ist, dass der Ansatz von PHP 5 ziemlich dumm war und zu allen möglichen seltsamen Randfällen führte, während der komplexere Ansatz von PHP 7 zu einem vorhersehbareren und konsistenteren Verhalten führt.

Als letzte Vorbemerkung sollte angemerkt werden, dass PHP Referenzzählung und Copy-on-Write verwendet, um den Speicher zu verwalten. Das heißt, wenn Sie einen Wert "kopieren", verwenden Sie den alten Wert einfach wieder und erhöhen seinen Referenzzähler (Refcount). Erst wenn Sie eine Änderung vornehmen, wird eine echte Kopie (als "Duplizierung" bezeichnet) erstellt. Sehen Sie werden belogen für eine umfangreichere Einführung zu diesem Thema.

PHP 5

Interner Array-Zeiger und HashPointer

Arrays in PHP 5 verfügen über einen dedizierten "Internal Array Pointer" (IAP), der Änderungen ordnungsgemäß unterstützt: Wenn ein Element entfernt wird, wird überprüft, ob der IAP auf dieses Element verweist. Wenn dies der Fall ist, wird es stattdessen zum nächsten Element weitergeleitet.

Während foreachder IAP verwendet wird, gibt es eine zusätzliche Komplikation: Es gibt nur einen IAP, aber ein Array kann Teil mehrerer foreachSchleifen sein:

// Using by-ref iteration here to make sure that it's really
// the same array in both loops and not a copy
foreach ($arr as &$v1) {
    foreach ($arr as &$v) {
        // ...
    }
}

Um zwei gleichzeitige Schleifen mit nur einem internen Array-Zeiger zu unterstützen, führen Sie foreachdie folgenden Spielereien aus: Bevor der Schleifenkörper ausgeführt wird, foreachwird ein Zeiger auf das aktuelle Element und seinen Hash in einem Per-Foreach gesichert HashPointer. Nachdem der Schleifenkörper ausgeführt wurde, wird der IAP auf dieses Element zurückgesetzt, wenn es noch vorhanden ist. Wenn das Element jedoch entfernt wurde, verwenden wir es nur dort, wo sich der IAP gerade befindet. Dieses Schema funktioniert meistens irgendwie, aber es gibt eine Menge seltsames Verhalten, das man daraus machen kann, von denen ich einige unten demonstrieren werde.

Array-Duplizierung

Der IAP ist ein sichtbares Merkmal eines Arrays (das durch die currentFunktionsfamilie verfügbar gemacht wird ), da solche Änderungen am IAP als Änderungen unter der Semantik des Kopierens beim Schreiben gelten. Dies bedeutet leider, dass foreachin vielen Fällen das Array, über das es iteriert, dupliziert werden muss. Die genauen Bedingungen sind:

  1. Das Array ist keine Referenz (is_ref = 0). Wenn es sich um eine Referenz ist, ändert sich dann auf sie sind angeblich zu verbreiten, so dass es nicht dupliziert werden soll.
  2. Das Array hat refcount> 1. Wenn refcount1 ist, wird das Array nicht freigegeben und wir können es direkt ändern.

Wenn das Array nicht dupliziert wird (is_ref = 0, refcount = 1), wird nur sein Array refcountinkrementiert (*). Wenn foreacheine Referenz verwendet wird, wird das (möglicherweise duplizierte) Array in eine Referenz umgewandelt.

Betrachten Sie diesen Code als Beispiel für eine Duplizierung:

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

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

Hier $arrwird dupliziert zu verhindern , auf IAP ändert $arrAuslaufen zu $outerArr. In Bezug auf die obigen Bedingungen ist das Array keine Referenz (is_ref = 0) und wird an zwei Stellen verwendet (refcount = 2). Diese Anforderung ist unglücklich und ein Artefakt der suboptimalen Implementierung (es gibt hier keine Bedenken hinsichtlich einer Änderung während der Iteration, sodass wir den IAP nicht wirklich verwenden müssen).

(*) Das Inkrementieren des refcounthier klingt harmlos, verstößt jedoch gegen die COW-Semantik (Copy-on-Write): Dies bedeutet, dass wir den IAP eines refcount = 2-Arrays ändern werden, während COW vorschreibt, dass Änderungen nur für refcount = durchgeführt werden können 1 Werte. Diese Verletzung führt zu einer vom Benutzer sichtbaren Verhaltensänderung (während eine COW normalerweise transparent ist), da die IAP-Änderung im iterierten Array beobachtet werden kann - jedoch nur bis zur ersten Nicht-IAP-Änderung im Array. Stattdessen wären die drei "gültigen" Optionen a) gewesen, um immer zu duplizieren, b) nicht zu erhöhen refcountund somit zuzulassen, dass das iterierte Array in der Schleife willkürlich geändert wird, oder c) den IAP überhaupt nicht zu verwenden (das PHP 7 Lösung).

Positionsvorschubreihenfolge

Es gibt ein letztes Implementierungsdetail, das Sie beachten müssen, um die folgenden Codebeispiele richtig zu verstehen. Die "normale" Art, eine Datenstruktur zu durchlaufen, würde im Pseudocode ungefähr so ​​aussehen:

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

Da foreaches sich jedoch um eine ganz besondere Schneeflocke handelt, geht man etwas anders vor:

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

Der Array-Zeiger wird nämlich bereits vorwärts bewegt, bevor der Schleifenkörper ausgeführt wird. Dies bedeutet, dass sich $ider IAP bereits am Element befindet , während der Schleifenkörper am Element arbeitet $i+1. Dies ist der Grund, warum Codebeispiele, die Änderungen während der Iteration zeigen, immer unsetdas nächste Element und nicht das aktuelle Element sind.

Beispiele: Ihre Testfälle

Die drei oben beschriebenen Aspekte sollen Ihnen einen weitgehend vollständigen Eindruck von den Besonderheiten der foreachImplementierung vermitteln, und wir können einige Beispiele diskutieren.

Das Verhalten Ihrer Testfälle ist an dieser Stelle einfach zu erklären:

  • In den Testfällen 1 und 2 $arraybeginnt mit refcount = 1, sodass es nicht dupliziert wird durch foreach: Nur das refcountwird inkrementiert. Wenn der Schleifenkörper anschließend das Array ändert (das an diesem Punkt refcount = 2 hat), tritt die Duplizierung an diesem Punkt auf. Foreach wird weiterhin an einer unveränderten Kopie von arbeiten $array.

  • In Testfall 3 wird das Array erneut nicht dupliziert, foreachwodurch der IAP der $arrayVariablen geändert wird. Am Ende der Iteration ist der IAP NULL (was bedeutet, dass die Iteration durchgeführt wurde), was eachdurch Zurückgeben angezeigt wird false.

  • In Testfällen 4 und 5 , die beide eachund resetsind Nebenreferenzfunktionen. Das $arrayhat ein, refcount=2wenn es an sie übergeben wird, also muss es dupliziert werden. Als solches foreachwird wieder an einem separaten Array gearbeitet.

Beispiele: Auswirkungen von currentin foreach

Eine gute Möglichkeit, die verschiedenen Duplizierungsverhalten zu zeigen, besteht darin, das Verhalten der current()Funktion innerhalb einer foreachSchleife zu beobachten . Betrachten Sie dieses Beispiel:

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

Hier sollten Sie wissen, dass current()es sich um eine By-Ref-Funktion handelt (eigentlich: Prefer-Ref), obwohl das Array dadurch nicht geändert wird. Es muss sein, um mit all den anderen Funktionen, wie sie nextalle by-ref sind, gut zu spielen. Das Übergeben von Referenzen impliziert, dass das Array getrennt werden muss und daher $arrayund das foreach-arraywird anders sein. Der Grund, den Sie 2anstelle von erhalten, 1ist auch oben erwähnt: foreachErweitert den Array-Zeiger vor dem Ausführen des Benutzercodes, nicht danach. Obwohl sich der Code im ersten Element befindet, wurde foreachder Zeiger bereits auf das zweite Element verschoben.

Versuchen wir nun eine kleine Modifikation:

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

Hier haben wir den Fall is_ref = 1, so dass das Array nicht kopiert wird (genau wie oben). Jetzt, da es sich um eine Referenz handelt, muss das Array nicht mehr dupliziert werden, wenn es an die by-ref- current()Funktion übergeben wird. Also current()und foreacharbeite am selben Array. Sie sehen jedoch immer noch das Off-by-One-Verhalten, da foreachder Zeiger vorwärts bewegt wird.

Sie erhalten das gleiche Verhalten, wenn Sie eine Iteration nach Referenz durchführen:

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

Hier ist der wichtige Teil, dass foreach $arrayein is_ref = 1 macht, wenn es durch Referenz iteriert wird, so dass Sie im Grunde die gleiche Situation wie oben haben.

Eine weitere kleine Variation, dieses Mal weisen wir das Array einer anderen Variablen zu:

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

Hier ist der Refcount von $array2, wenn die Schleife gestartet wird, also müssen wir die Duplizierung einmal im Voraus durchführen. Somit ist $arraydas von foreach verwendete Array von Anfang an vollständig getrennt. Aus diesem Grund erhalten Sie die Position des IAP an der Stelle, an der sie sich vor der Schleife befand (in diesem Fall an der ersten Position).

Beispiele: Änderung während der Iteration

Der Versuch, Änderungen während der Iteration zu berücksichtigen, ist der Ursprung all unserer foreach-Probleme. Daher werden einige Beispiele für diesen Fall betrachtet.

Betrachten Sie diese verschachtelten Schleifen über dasselbe Array (wobei die By-Ref-Iteration verwendet wird, um sicherzustellen, dass es wirklich dasselbe ist):

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

// Output: (1, 1) (1, 3) (1, 4) (1, 5)

Der erwartete Teil hier ist, dass (1, 2)er in der Ausgabe fehlt, weil das Element 1entfernt wurde. Was wahrscheinlich unerwartet ist, ist, dass die äußere Schleife nach dem ersten Element stoppt. Warum ist das so?

Der Grund dafür ist der oben beschriebene Nested-Loop-Hack: Bevor der Loop-Body ausgeführt wird, werden die aktuelle IAP-Position und der aktuelle Hash in a gesichert HashPointer. Nach dem Schleifenkörper wird er wiederhergestellt, jedoch nur, wenn das Element noch vorhanden ist. Andernfalls wird stattdessen die aktuelle IAP-Position (wie auch immer) verwendet. Im obigen Beispiel ist dies genau der Fall: Das aktuelle Element der äußeren Schleife wurde entfernt, sodass der IAP verwendet wird, der bereits von der inneren Schleife als fertig markiert wurde!

Eine weitere Konsequenz des HashPointerBackup + Restore-Mechanismus ist, dass Änderungen am IAP über reset()usw. normalerweise keine Auswirkungen haben foreach. Der folgende Code wird beispielsweise so ausgeführt, als ob der reset()überhaupt nicht vorhanden wäre:

$array = [1, 2, 3, 4, 5];
foreach ($array as &$value) {
    var_dump($value);
    reset($array);
}
// output: 1, 2, 3, 4, 5

Der Grund dafür ist, dass reset()der IAP zwar vorübergehend geändert wird, jedoch nach dem Schleifenkörper im aktuellen foreach-Element wiederhergestellt wird. Um reset()eine Auswirkung auf die Schleife zu erzwingen , müssen Sie zusätzlich das aktuelle Element entfernen, damit der Sicherungs- / Wiederherstellungsmechanismus fehlschlägt:

$array = [1, 2, 3, 4, 5];
$ref =& $array;
foreach ($array as $value) {
    var_dump($value);
    unset($array[1]);
    reset($array);
}
// output: 1, 1, 3, 4, 5

Aber diese Beispiele sind immer noch vernünftig. Der wahre Spaß beginnt, wenn Sie sich daran erinnern, dass die HashPointerWiederherstellung einen Zeiger auf das Element und seinen Hash verwendet, um festzustellen, ob es noch vorhanden ist. Aber: Hashes haben Kollisionen und Zeiger können wiederverwendet werden! Dies bedeutet, dass wir bei sorgfältiger Auswahl der Array-Schlüssel davon ausgehen können, foreachdass ein entferntes Element noch vorhanden ist, sodass es direkt dorthin springt. Ein Beispiel:

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

Hier sollten wir normalerweise die Ausgabe 1, 1, 3, 4gemäß den vorherigen Regeln erwarten . Was passiert, ist, dass 'FYFY'es denselben Hash wie das entfernte Element 'EzFY'hat und der Allokator zufällig denselben Speicherort zum Speichern des Elements wiederverwendet. Foreach springt also direkt zum neu eingefügten Element und verkürzt so die Schleife.

Ersetzen der iterierten Entität während der Schleife

Ein letzter seltsamer Fall, den ich erwähnen möchte, ist, dass Sie mit PHP die iterierte Entität während der Schleife ersetzen können. Sie können also mit der Iteration eines Arrays beginnen und es dann zur Hälfte durch ein anderes Array ersetzen. Oder beginnen Sie mit der Iteration eines Arrays und ersetzen Sie es durch ein Objekt:

$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;
    }
}
/* Output: 1 2 3 6 7 8 9 10 */

Wie Sie in diesem Fall sehen können, beginnt PHP von Anfang an mit der Iteration der anderen Entität, sobald die Ersetzung erfolgt ist.

PHP 7

Hashtable-Iteratoren

Wenn Sie sich noch erinnern, bestand das Hauptproblem bei der Array-Iteration darin, wie Elemente während der Iteration entfernt werden. PHP 5 verwendete zu diesem Zweck einen einzelnen internen Array-Zeiger (IAP), der etwas suboptimal war, da ein Array-Zeiger gestreckt werden musste, um mehrere gleichzeitige foreach-Schleifen und die Interaktion mit reset()usw. darüber hinaus zu unterstützen.

PHP 7 verwendet einen anderen Ansatz, nämlich die Erstellung einer beliebigen Anzahl externer, sicherer Hashtabellen-Iteratoren. Diese Iteratoren müssen im Array registriert sein. Ab diesem Zeitpunkt haben sie dieselbe Semantik wie der IAP: Wenn ein Array-Element entfernt wird, werden alle Hashtabellen-Iteratoren, die auf dieses Element verweisen, zum nächsten Element weitergeleitet.

Dies bedeutet, dass foreachder IAP überhaupt nicht mehr verwendet wird . Die foreachSchleife hat absolut keinen Einfluss auf die Ergebnisse von current()usw. und ihr eigenes Verhalten wird niemals durch Funktionen wie reset()usw. beeinflusst.

Array-Duplizierung

Eine weitere wichtige Änderung zwischen PHP 5 und PHP 7 betrifft die Duplizierung von Arrays. Nachdem der IAP nicht mehr verwendet wird, führt die Array-Iteration nach Wert refcountin allen Fällen nur noch ein Inkrement durch (anstatt das Array zu duplizieren). Wenn das Array während der foreachSchleife geändert wird, tritt zu diesem Zeitpunkt eine Duplizierung auf (gemäß Copy-on-Write) und foreacharbeitet weiter am alten Array.

In den meisten Fällen ist diese Änderung transparent und hat keinen anderen Effekt als eine bessere Leistung. Es gibt jedoch eine Gelegenheit, bei der es zu einem anderen Verhalten kommt, nämlich in dem Fall, in dem das Array zuvor eine Referenz war:

$array = [1, 2, 3, 4, 5];
$ref = &$array;
foreach ($array as $val) {
    var_dump($val);
    $array[2] = 0;
}
/* Old output: 1, 2, 0, 4, 5 */
/* New output: 1, 2, 3, 4, 5 */

Bisher war die Iteration von Referenz-Arrays nach Wert Sonderfälle. In diesem Fall ist keine Duplizierung aufgetreten, sodass alle Änderungen des Arrays während der Iteration von der Schleife wiedergegeben werden. In PHP 7 ist dieser Sonderfall weg: Eine Iteration eines Arrays nach Wert arbeitet immer an den ursprünglichen Elementen, ohne Änderungen während der Schleife zu berücksichtigen.

Dies gilt natürlich nicht für die Iteration nach Referenz. Wenn Sie per Referenz iterieren, werden alle Änderungen von der Schleife übernommen. Interessanterweise gilt das Gleiche für die By-Value-Iteration einfacher Objekte:

$obj = new stdClass;
$obj->foo = 1;
$obj->bar = 2;
foreach ($obj as $val) {
    var_dump($val);
    $obj->bar = 42;
}
/* Old and new output: 1, 42 */

Dies spiegelt die By-Handle-Semantik von Objekten wider (dh sie verhalten sich selbst in By-Value-Kontexten referenzartig).

Beispiele

Betrachten wir einige Beispiele, beginnend mit Ihren Testfällen:

  • Die Testfälle 1 und 2 behalten dieselbe Ausgabe bei: Die Array-Iteration nach Wert arbeitet immer an den ursprünglichen Elementen. (In diesem Fall ist das gerade refcountingund doppelte Verhalten zwischen PHP 5 und PHP 7 genau gleich.)

  • Änderungen an Testfall 3: ForeachVerwendet den IAP nicht mehr und each()ist daher von der Schleife nicht betroffen. Es wird vorher und nachher die gleiche Ausgabe haben.

  • Testfälle 4 und 5 gleich bleiben: each()und reset()das Array duplizieren , bevor die IAP zu ändern, während foreachimmer noch das Original - Array verwendet. (Nicht, dass die IAP-Änderung von Bedeutung gewesen wäre, selbst wenn das Array gemeinsam genutzt worden wäre.)

Die zweite Reihe von Beispielen bezog sich auf das Verhalten current()unter verschiedenen reference/refcountingKonfigurationen. Dies ist nicht mehr sinnvoll, da current()es von der Schleife völlig unberührt bleibt und der Rückgabewert immer gleich bleibt.

Wir erhalten jedoch einige interessante Änderungen, wenn wir Änderungen während der Iteration berücksichtigen. Ich hoffe, Sie finden das neue Verhalten vernünftiger. Das erste Beispiel:

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

// Old output: (1, 1) (1, 3) (1, 4) (1, 5)
// New output: (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) 

Wie Sie sehen, wird die äußere Schleife nach der ersten Iteration nicht mehr abgebrochen. Der Grund dafür ist, dass beide Schleifen jetzt vollständig separate Hashtabellen-Iteratoren haben und keine Kreuzkontamination mehr beider Schleifen durch einen gemeinsam genutzten IAP mehr besteht.

Ein weiterer seltsamer Randfall, der jetzt behoben ist, ist der seltsame Effekt, den Sie erhalten, wenn Sie Elemente entfernen und hinzufügen, die zufällig denselben Hash haben:

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

Zuvor sprang der HashPointer-Wiederherstellungsmechanismus direkt auf das neue Element, da es "aussah", als wäre es dasselbe wie das entfernte Element (aufgrund von kollidierendem Hash und Zeiger). Da wir uns für nichts mehr auf den Element-Hash verlassen, ist dies kein Problem mehr.

NikiC
quelle
4
@Baba Das tut es. Das Übergeben an eine Funktion ist dasselbe wie $foo = $arrayvor der Schleife;)
NikiC
32
Für diejenigen unter Ihnen, die nicht wissen, was ein Zval ist, lesen Sie bitte Sara Golemans blog.golemon.com/2007/01/youre-being-lied-to.html
shu zOMG chen
1
Kleinere Korrektur: Was Sie Bucket nennen, ist nicht das, was in einer Hashtabelle normalerweise Bucket genannt wird. Normalerweise besteht Bucket aus einer Reihe von Einträgen mit derselben Hash-Größe. Sie scheinen es für einen Eintrag zu verwenden, der normalerweise als Eintrag bezeichnet wird. Die verknüpfte Liste befindet sich nicht in Eimern, sondern in Einträgen.
unbeli
12
@unbeli Ich verwende die von PHP intern verwendete Terminologie. Die Buckets sind Teil einer doppelt verknüpften Liste für Hash-Kollisionen und auch Teil einer doppelt verknüpften Liste für Bestellungen;)
NikiC
4
Großartige Antwort. Ich denke du meintest iterate($outerArr);und nicht iterate($arr);irgendwo.
Niahoo
116

In Beispiel 3 ändern Sie das Array nicht. In allen anderen Beispielen ändern Sie entweder den Inhalt oder den internen Array-Zeiger. Dies ist bei PHP- Arrays aufgrund der Semantik des Zuweisungsoperators wichtig .

Der Zuweisungsoperator für die Arrays in PHP funktioniert eher wie ein fauler Klon. Wenn Sie eine Variable einer anderen zuweisen, die ein Array enthält, wird das Array im Gegensatz zu den meisten Sprachen geklont. Das eigentliche Klonen wird jedoch nur durchgeführt, wenn es benötigt wird. Dies bedeutet, dass der Klon nur stattfindet, wenn eine der Variablen geändert wird (Copy-on-Write).

Hier ist ein Beispiel:

$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.

Wenn Sie zu Ihren Testfällen zurückkehren, können Sie sich leicht vorstellen, dass foreacheine Art Iterator mit einem Verweis auf das Array erstellt wird. Diese Referenz funktioniert genau wie die Variable $bin meinem Beispiel. Der Iterator und die Referenz leben jedoch nur während der Schleife, und dann werden beide verworfen. Jetzt können Sie sehen, dass das Array in allen Fällen außer 3 während der Schleife geändert wird, während diese zusätzliche Referenz aktiv ist. Dies löst einen Klon aus und das erklärt, was hier los ist!

Hier ist ein ausgezeichneter Artikel für einen weiteren Nebeneffekt dieses Copy-on-Write-Verhaltens: Der ternäre PHP-Operator: Schnell oder nicht?

linepogl
quelle
scheint Ihr Recht zu sein, ich habe ein Beispiel gemacht, das zeigt, dass: codepad.org/OCjtvu8r ein Unterschied zu Ihrem Beispiel ist - es wird nicht kopiert, wenn Sie den Wert ändern, sondern nur, wenn Sie die Schlüssel ändern.
zb '
Dies erklärt in der Tat das ganze Verhalten zeigen oben, und es kann gut durch den Aufruf veranschaulicht werden each()am Ende des ersten Testfall, wo wir sehen , dass der Array - Zeiger der ursprünglichen Array zeigt auf das zweite Element, da das Array geändert wurde während die erste Iteration. Dies scheint auch zu demonstrieren, dass foreachder Array-Zeiger vor dem Ausführen des Codeblocks der Schleife weiter bewegt wird, was ich nicht erwartet hatte - ich hätte gedacht, dass dies am Ende der Fall sein würde. Vielen Dank, das klärt es für mich schön auf.
Dave Random
49

Einige Punkte, die Sie bei der Arbeit beachten sollten foreach():

a) foreacharbeitet an der voraussichtlichen Kopie des ursprünglichen Arrays. foreach()Dies bedeutet, dass die gemeinsame Nutzung von Daten so lange gespeichert wird, bis für jede Notizen / Benutzerkommentare eine erstellt prospected copywird .

b) Was löst eine prospektierte Kopie aus ? Eine prospektierte Kopie wird basierend auf der Richtlinie erstellt copy-on-write, dh, wann immer ein übergebenes Array foreach()geändert wird, wird ein Klon des ursprünglichen Arrays erstellt.

c) Das ursprüngliche Array und der foreach()Iterator haben DISTINCT SENTINEL VARIABLES, dh eines für das ursprüngliche Array und eines für foreach; Siehe den Testcode unten. SPL , Iteratoren und Array-Iterator .

Frage zum Stapelüberlauf Wie kann sichergestellt werden, dass der Wert in einer 'foreach'-Schleife in PHP zurückgesetzt wird? befasst sich mit den Fällen (3,4,5) Ihrer Frage.

Das folgende Beispiel zeigt, dass each () und reset () KEINE SENTINELVariablen (for example, the current index variable)des foreach()Iterators beeinflussen.

$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/>";

Ausgabe:

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
sakhunzai
quelle
2
Ihre Antwort ist nicht ganz richtig. foreacharbeitet mit einer potenziellen Kopie des Arrays, erstellt jedoch nur dann die eigentliche Kopie, wenn sie benötigt wird.
Linepogl
Möchten Sie zeigen, wie und wann diese potenzielle Kopie durch Code erstellt wird? Mein Code zeigt, dass foreachdas Array 100% der Zeit kopiert wird. Ich bin gespannt zu wissen. Vielen Dank für
Ihre
Das Kopieren eines Arrays kostet viel. Zählen Sie die Zeit, die zum Iterieren eines Arrays mit 100000 Elementen mit entweder foroder benötigt wird foreach. Sie werden keinen signifikanten Unterschied zwischen den beiden feststellen, da keine tatsächliche Kopie stattfindet.
Linepogl
Dann würde ich annehmen, dass es SHARED data storagebis oder bis reserviert ist copy-on-write, aber (aus meinem Code-Snippet) es ist offensichtlich, dass es immer ZWEI Sätze von SENTINEL variableseinem für den original arrayanderen und für den anderen geben wird foreach. Danke, das macht Sinn
sakhunzai
1
Ja, das ist eine "voraussichtliche" Kopie, dh eine "potenzielle" Kopie. Sie ist nicht geschützt, wie Sie vorgeschlagen haben
Sakhunzai,
33

HINWEIS FÜR PHP 7

So aktualisieren Sie diese Antwort, da sie an Popularität gewonnen hat: Diese Antwort gilt ab PHP 7 nicht mehr. Wie unter " Rückwärts inkompatible Änderungen " erläutert , funktioniert in PHP 7 foreach die Kopie des Arrays, sodass alle Änderungen am Array selbst vorgenommen werden werden nicht in jeder Schleife wiedergegeben. Weitere Details unter dem Link.

Erklärung (Zitat aus php.net ):

Das erste Formular durchläuft das durch array_expression angegebene Array. Bei jeder Iteration wird der Wert des aktuellen Elements dem Wert $ zugewiesen und der interne Array-Zeiger um eins vorgerückt (bei der nächsten Iteration sehen Sie sich also das nächste Element an).

In Ihrem ersten Beispiel haben Sie also nur ein Element im Array, und wenn der Zeiger verschoben wird, ist das nächste Element nicht vorhanden. Nachdem Sie also ein neues Element hinzugefügt haben, endet jedes Element, da es bereits "entschieden" hat, dass es das letzte Element ist.

In Ihrem zweiten Beispiel beginnen Sie mit zwei Elementen, und foreach-Schleife befindet sich nicht am letzten Element, sodass das Array bei der nächsten Iteration ausgewertet wird und somit erkannt wird, dass das Array ein neues Element enthält.

Ich glaube, dass dies alles eine Folge von Bei jedem Iterationsteil der Erklärung in der Dokumentation ist, was wahrscheinlich bedeutet, dass foreachdie gesamte Logik ausgeführt wird, bevor der Code aufgerufen wird {}.

Testfall

Wenn Sie dies ausführen:

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

Sie erhalten diese Ausgabe:

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

Dies bedeutet, dass es die Änderung akzeptiert und durchlaufen hat, weil es "rechtzeitig" geändert wurde. Aber wenn Sie dies tun:

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

Sie erhalten:

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

Das bedeutet, dass das Array geändert wurde, aber da wir es geändert haben, als sich das foreachbereits am letzten Element des Arrays befand, hat es "beschlossen", keine Schleife mehr durchzuführen, und obwohl wir ein neues Element hinzugefügt haben, haben wir es "zu spät" hinzugefügt und es wurde nicht durchgeschleift.

Eine ausführliche Erklärung finden Sie unter Wie funktioniert PHP 'foreach' tatsächlich? Das erklärt die Interna hinter diesem Verhalten.

dkasipovic
quelle
7
Hast du den Rest der Antwort gelesen? Es ist durchaus sinnvoll, dass foreach entscheidet, ob eine weitere Schleife ausgeführt wird, bevor der darin enthaltene Code überhaupt ausgeführt wird.
Dkasipovic
2
Nein, das Array wird geändert, aber "zu spät", da foreach bereits "denkt", dass es sich um das letzte Element handelt (das sich zu Beginn der Iteration befindet) und keine Schleife mehr ausführt. Im zweiten Beispiel befindet es sich zu Beginn der Iteration nicht am letzten Element und wird beim Beginn der nächsten Iteration erneut ausgewertet. Ich versuche einen Testfall vorzubereiten.
Dkasipovic
1
@AlmaDo Schauen Sie sich lxr.php.net/xref/PHP_TRUNK/Zend/zend_vm_def.h#4509 an . Es wird immer auf den nächsten Zeiger gesetzt, wenn es iteriert. Wenn die letzte Iteration erreicht ist, wird sie als beendet markiert (über den NULL-Zeiger). Wenn Sie dann in der letzten Iteration einen Schlüssel hinzufügen, wird er von foreach nicht bemerkt.
Bwoebi
1
@DKasipovic nein. Es gibt dort keine vollständige und klare Erklärung (zumindest für den Moment - vielleicht irre ich mich)
Alma Do
4
Eigentlich scheint es, dass @AlmaDo einen Fehler im Verständnis seiner eigenen Logik hat ... Ihre Antwort ist in Ordnung.
Bwoebi
15

Gemäß der Dokumentation im PHP-Handbuch.

Bei jeder Iteration wird der Wert des aktuellen Elements $ v zugewiesen und der interne
Array-Zeiger um eins vorgerückt (bei der nächsten Iteration sehen Sie sich also das nächste Element an).

Also wie in Ihrem ersten Beispiel:

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

$arrayhaben nur ein einzelnes Element, also gemäß der foreach-Ausführung 1 zuweisen $vund es hat kein anderes Element, um den Zeiger zu bewegen

Aber in Ihrem zweiten Beispiel:

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

$arrayHaben Sie zwei Elemente, also wertet $ array jetzt die Nullindizes aus und bewegt den Zeiger um eins. Für die erste Iteration der Schleife $array['baz']=3;als Referenzübergabe hinzugefügt .

user3535130
quelle
13

Gute Frage, denn viele Entwickler, auch erfahrene, sind verwirrt darüber, wie PHP Arrays in foreach-Schleifen verarbeitet. In der Standard-foreach-Schleife erstellt PHP eine Kopie des Arrays, das in der Schleife verwendet wird. Die Kopie wird sofort nach Beendigung der Schleife verworfen. Dies ist beim Betrieb einer einfachen foreach-Schleife transparent. Zum Beispiel:

$set = array("apple", "banana", "coconut");
foreach ( $set AS $item ) {
    echo "{$item}\n";
}

Dies gibt aus:

apple
banana
coconut

Die Kopie wird also erstellt, aber der Entwickler bemerkt es nicht, da das ursprüngliche Array nicht innerhalb der Schleife oder nach Beendigung der Schleife referenziert wird. Wenn Sie jedoch versuchen, die Elemente in einer Schleife zu ändern, stellen Sie fest, dass sie am Ende nicht geändert werden:

$set = array("apple", "banana", "coconut");
foreach ( $set AS $item ) {
    $item = strrev ($item);
}

print_r($set);

Dies gibt aus:

Array
(
    [0] => apple
    [1] => banana
    [2] => coconut
)

Änderungen gegenüber dem Original können keine Hinweise sein. Tatsächlich gibt es keine Änderungen gegenüber dem Original, obwohl Sie $ item eindeutig einen Wert zugewiesen haben. Dies liegt daran, dass Sie mit $ item arbeiten, wie es in der Kopie von $ set angezeigt wird, an der gearbeitet wird. Sie können dies überschreiben, indem Sie $ item als Referenz abrufen, wie folgt:

$set = array("apple", "banana", "coconut");
foreach ( $set AS &$item ) {
    $item = strrev($item);
}
print_r($set);

Dies gibt aus:

Array
(
    [0] => elppa
    [1] => ananab
    [2] => tunococ
)

Es ist also offensichtlich und beobachtbar, dass die an $ item vorgenommenen Änderungen an den Mitgliedern des ursprünglichen $ -Satzes vorgenommen werden, wenn $ item als Referenz bearbeitet wird. Die Verwendung von $ item als Referenz verhindert auch, dass PHP die Array-Kopie erstellt. Um dies zu testen, zeigen wir zunächst ein kurzes Skript, das die Kopie demonstriert:

$set = array("apple", "banana", "coconut");
foreach ( $set AS $item ) {
    $set[] = ucfirst($item);
}
print_r($set);

Dies gibt aus:

Array
(
    [0] => apple
    [1] => banana
    [2] => coconut
    [3] => Apple
    [4] => Banana
    [5] => Coconut
)

Wie im Beispiel gezeigt, hat PHP $ set kopiert und zum Schleifen verwendet. Wenn jedoch $ set innerhalb der Schleife verwendet wurde, fügte PHP die Variablen dem ursprünglichen Array hinzu, nicht dem kopierten Array. Grundsätzlich verwendet PHP das kopierte Array nur zur Ausführung der Schleife und zur Zuweisung von $ item. Aus diesem Grund wird die obige Schleife nur dreimal ausgeführt und jedes Mal wird ein anderer Wert an das Ende des ursprünglichen $ -Satzes angehängt, wobei der ursprüngliche $ -Satz mit 6 Elementen belassen wird, jedoch niemals eine Endlosschleife betreten wird.

Was wäre jedoch, wenn wir, wie bereits erwähnt, $ item als Referenz verwendet hätten? Ein einzelnes Zeichen zum obigen Test hinzugefügt:

$set = array("apple", "banana", "coconut");
foreach ( $set AS &$item ) {
    $set[] = ucfirst($item);
}
print_r($set);

Ergibt eine Endlosschleife. Beachten Sie, dass dies tatsächlich eine Endlosschleife ist. Sie müssen das Skript entweder selbst beenden oder warten, bis Ihr Betriebssystem nicht mehr über genügend Arbeitsspeicher verfügt. Ich habe meinem Skript die folgende Zeile hinzugefügt, damit PHP sehr schnell keinen Speicher mehr hat. Ich schlage vor, dass Sie dasselbe tun, wenn Sie diese Endlosschleifentests ausführen:

ini_set("memory_limit","1M");

In diesem vorherigen Beispiel mit der Endlosschleife sehen wir den Grund, warum PHP geschrieben wurde, um eine Kopie des Arrays zum Überlaufen zu erstellen. Wenn eine Kopie nur von der Struktur des Schleifenkonstrukts selbst erstellt und verwendet wird, bleibt das Array während der Ausführung der Schleife statisch, sodass Sie nie auf Probleme stoßen.

Hrvoje Antunović
quelle
7

PHP foreach loop kann mit Indexed arrays, Associative arraysund verwendet werden Object public variables.

In der foreach-Schleife erstellt php als erstes eine Kopie des Arrays, über das iteriert werden soll. PHP iteriert dann über dieses neue copyArray und nicht über das ursprüngliche. Dies wird im folgenden Beispiel gezeigt:

<?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).

Außerdem erlaubt PHP auch die Verwendung iterated values as a reference to the original array value. Dies wird unten gezeigt:

<?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

Hinweis: Es original array indexesdarf nicht als verwendet werden references.

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

Pranav Rana
quelle
1
Object public variablesist falsch oder bestenfalls irreführend. Sie können ein Objekt in einem Array nicht ohne die richtige Schnittstelle (z. B. Traversible) verwenden, und wenn Sie dies tun foreach((array)$obj ..., arbeiten Sie tatsächlich mit einem einfachen Array, nicht mehr mit einem Objekt.
Christian