Seltsames Verhalten bei foreach

swas

Mitglied
Hallo Leute,
ich habe einen echt seltsamen Effekt bei meiner foreach-Schleife und kann leider dazu nichts finden.

Also ich habe ein mehrdimensionales Array und gebe es mit foreach aus. Das seltsame ist, dass mein vorletzter Eintrag doppelt ausgegeben wird und der Letzte nicht auftaucht.

Hier ist der PHP-Code dazu:
PHP:
        echo "<br> <pre>";
        print_r ( $jahr );
        echo "<br>";
       
        foreach ($jahr as $kw) {
       
            // Wochentag als Text mit Datum
            echo "<br> ".$kw['Tag'].", ".date('d.m.Y', strtotime($kw['Datum']));
        }

Das print_r gib folgendes aus:
Code:
Array
(
    [0] => Array
        (
            [Datum] => 2014-01-06
            [Tag] => 1
        )

    [1] => Array
        (
            [Datum] => 2014-01-13
            [Tag] => 1
        )

    [2] => Array
        (
            [Datum] => 2014-01-20
            [Tag] => 1
        )

    [3] => Array
        (
            [Datum] => 2014-01-27
            [Tag] => 1
        )

    [4] => Array
        (
            [Datum] => 2014-02-03
            [Tag] => 1
        )
)

Und die foreach gibt alles bis zur vierten Stelle (also [3]) sauber aus und dann gibt er die Stelle noch einmal aus. Die fünfte Stelle (also [4]) gibt er gar nicht aus.
Das kann doch eigentlich nicht sein, oder?
Würde mich freuen wenn mir das wer erklären könnte ;)

Gruß,
 
Das ich dich richtig Verstehe.
Er gibt 0-3 normal aus, anschließend wird 3 nochmal ausgegeben und die 4 wird garnicht??
 
Ist das der komplette Code der Schleife oder hast du das gekürzt? Kannst du mal ein komplettes Bespiel inkl. Array-Aufbau posten? Wenn foreach ein Problem hätte, wäre das bekannt. Ich denke eher, das die Ursache im Code zu finden ist, den du nicht gepostet hast.
 
Hi,
tut mir leid, dass ich jetzt erst antworte.. Bei mir ging es die Tage ein wenig drunter und drüber.
@merzi86
Jap, das ist genau das verhalten.
@saftmeister
Der Code ist einwenig angepasst. Das Array baue ich mithilfe einer Schleife auf, da die Informationen aus einer Datenbank kommen. Ich hab ein paar Felder weggelassen, da diese nicht zu dem Problem führen.
Die Stelle gefunden, die dafür verantwortlich ist und zwei Lösungen gefunden. Leider verstehe ich das noch immer nicht so ganz aber naja...

Hier mal der komplette Code und die Ausgabe dazu.

PHP:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Jahresansicht</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
</head>
<body>
<?php

   $jahr = array();

   $jahr[] = array('Datum' => '2014-01-06', 'Leertag' => 1);
   $jahr[] = array('Datum' => '2014-01-07', 'Leertag' => 2);
   $jahr[] = array('Datum' => '2014-01-11', 'Leertag' => 5);
   $jahr[] = array('Datum' => '2014-01-12', 'Leertag' => 6);
   $jahr[] = array('Datum' => '2014-01-13', 'Leertag' => 7);
  
   echo "<br> Erste foreach Schleife ohne Ausgabe";
   foreach ($jahr as &$kw) {
     if ($kw['Datum'] == '2014-01-07') {
       // Datum und Leertag um einen Tag erhöhen
       $kw['Datum'] = date('Y-m-d', strtotime($kw['Datum'].'+ 1 day'));
       $kw['Leertag']++;
     }
   }
  
   // Lösung des Problems durch ein einfaches unset.....
   // unset($kw);

   echo "<pre>";
   print_r ( $jahr );
   echo "</pre>";
  
   echo "<br> Nun die Ausgabe mit Zählervariable";
   for ($i = 0; $i < count($jahr); $i++) {
     // Wochentag als Text mit Datum
     echo "<br> ".$jahr[$i]['Leertag'].", ".date('d.m.Y', strtotime($jahr[$i]['Datum']));
   }
  
   echo "<br><br> Zweite foreach Schleife";
   // Funktioniert seltsamerweise nicht richtig
   foreach ($jahr as $kw) {
     // Wochentag als Text mit Datum
     echo "<br> ".$kw['Leertag'].", ".date('d.m.Y', strtotime($kw['Datum']));
   }
  
?>
</body></html>

Code:
Erste foreach Schleife ohne Ausgabe

Array
(
  [0] => Array
  (
  [Datum] => 2014-01-06
  [Leertag] => 1
  )

  [1] => Array
  (
  [Datum] => 2014-01-08
  [Leertag] => 3
  )

  [2] => Array
  (
  [Datum] => 2014-01-11
  [Leertag] => 5
  )

  [3] => Array
  (
  [Datum] => 2014-01-12
  [Leertag] => 6
  )

  [4] => Array
  (
  [Datum] => 2014-01-13
  [Leertag] => 7
  )
)

Nun die Ausgabe mit Zählervariable
1, 06.01.2014
3, 08.01.2014
5, 11.01.2014
6, 12.01.2014
7, 13.01.2014

Zweite foreach Schleife
1, 06.01.2014
3, 08.01.2014
5, 11.01.2014
6, 12.01.2014
6, 12.01.2014

Der Fehler entsteht scheinbar durch eine foreach-Schleife die ich vorher durchlaufe und zwar so:

PHP:
echo "<br> Erste foreach Schleife";
foreach ($jahr as &$kw) {
   if ($kw['Datum'] == '2014-01-07') {
     // Datum und Leertag um einen Tag erhöhen
     $kw['Datum'] = date('Y-m-d', strtotime($kw['Datum'].'+ 1 day'));
     $kw['Leertag']++;
   }
}

Selbst wenn ich die if-Anweisung weglasse kommt der Fehler. Ich vermute der entsteht durch das &$kw... &$kw nutzt hier keine Kopie des Wertes sondern die Referenz, was mir ermöglicht die Werte des Arrays im Array zu ändern. Das war meiner Meinung nach die eleganteste Lösung deshalb habe ich die auch genutzt.

Gelöst habe ich mein Problem, indem ich eine ganz normale for-Schleife mit Zähler genutzt habe also so:

PHP:
echo "<br> Nun die Ausgabe mit Zählervariable";
for ($i = 0; $i < count($jahr); $i++) {
   // Wochentag als Text mit Datum
   echo "<br> ".$jahr[$i]['Leertag'].", ".date('d.m.Y', strtotime($jahr[$i]['Datum']));
}

Das Funktioniert wunderbar. Aber nur wenn vor der for-Schleife nicht noch eine foreach-Schleife ist, ansonsten gibt auch die for-Schleife zwei mal den vorletzten Wert aus und das obwohl man über den Index geht... Erklär mir das mal bitte einer. Wer lustig ist kann es ja mal ausprobieren. Tauscht einfach die Reihenfolge der Schleifen im oberen Code aus.

Die zweite Lösung, und vermutlich die bessere, ist es einfach die Variable &$kw nach der ersten foreach-Schleife mit unset() zu löschen.

Keine Ahnung was PHP da treibt.. oder ob ich da irgend einen Schwachsinn gebaut habe. Das ist auch genau der Punkt wieso ich PHP nicht so mag. Ich weiß oftmals nicht wirklich was da getrieben wird.
 
Es gibt einen Bugtrag-Eintrag, wobei das Verhalten nicht als Bug definiert sondern als gewünschtes Verhalten:

https://bugs.php.net/bug.php?id=29992

Du könntest einen Zähler bei der foreach einbauen.

PHP:
<?php
  $jahr = array();
  $jahr[] = array('Datum' => '2014-01-06', 'Leertag' => 1);
  $jahr[] = array('Datum' => '2014-01-07', 'Leertag' => 2);
  $jahr[] = array('Datum' => '2014-01-11', 'Leertag' => 5);
  $jahr[] = array('Datum' => '2014-01-12', 'Leertag' => 6);
  $jahr[] = array('Datum' => '2014-01-13', 'Leertag' => 7);
  var_dump($jahr);
   
  $i = 0;
  echo "<br> Erste foreach Schleife ohne Ausgabe";
  foreach ($jahr as $kw) {
  if ($kw['Datum'] == '2014-01-07') {
  // Datum und Leertag um einen Tag erhöhen
  $jahr[$i]['Datum'] = date('Y-m-d', strtotime($kw['Datum'].'+ 1 day'));
  $jahr[$i]['Leertag']++;
  }
  $i++;
  }
   
  var_dump($jahr);
 
Zuletzt bearbeitet:
swas hat gesagt.:
Die zweite Lösung, und vermutlich die bessere, ist es einfach die Variable &$kw nach der ersten foreach-Schleife mit unset() zu löschen.

Ja, das ist die übliche Lösung.

Siehe etwa auch hier („Referenzen und foreach-Schleifen“):

- http://www.php.de/adventskalender-2010/75163-4-1-dont-panic-hitchhikers-guide-php-oddities.html

Also, das solltest du dir so angewöhnen: Wenn du mit Referenzen in foreach-Schleifen arbeitest, einfach immer ein unset($ref); hinter die Schleife.

PS: Ich würde das durchaus auch als erwartbares Verhalten bezeichnen. Eine wesentliche „Unlogik“ sehe ich an der Stelle nicht.
 
Zuletzt bearbeitet:
Dann habe ich ja zufällig die ordentliche Lösung erwischt. ;)

PS: Ich würde das durchaus auch als erwartbares Verhalten bezeichnen. Eine wesentliche „Unlogik“ sehe ich an der Stelle nicht.
Naja ich bin hier an die Sache mit der Logik der for-Schleife gegangen. Die Zählervariable, die ich in der for-Schleife deklariere, die geht ja eigentlich nicht über die Schleife hinaus. In diesem speziellen Fall ist es wohl ein wenig anders.

Trotzdem danke euch allen für die Hilfe ;)
 
Der Vollständigkeit halber: Es gibt in PHP überhaupt keine Sub-Geltungsbereiche (etwa durch Kontrollstrukturen wie for, if, …) innerhalb von Funktions-/Methoden-/Closure-Körpern. Sobald du – in beliebiger Schachtelung – eine Variable erstellst, steht sie jedem nachfolgenden Code innerhalb der Funktion/Methode/Closure zur Verfügung, auch wenn der übergeordnet ist. (Mal abgesehen von Funktionsdefinitionen innerhalb von Funktionen und ähnlichen Scherzen.)

Das ist anders als in etwa C, ja. Es ist auch mit Sicherheit kein guter Stil, dieses „Feature“ zu nutzen. Ich denke, so was wird zu einem signifikanten Anteil deshalb so gemacht, weil es in dem Kontext, wie PHP-Programme ausgeführt werden, einfach Geschwindigkeitsvorteile hat.
 
Zurück