Jörg: php8: while list each

Hallo,

ich bin grad etwas unsicher, ob ich diesen Thtread weiter nutzen sollte oder einen neuen aufmachen soll. Da es sich aber weiterhin um ein Problem im Rahmen der Umstellung auf php8 handelt, mache ich mal einen weiteren "Subthread" auf.

Edit Rolf B: nach etwas Nachdenken hab ich den Thread lieber geteilt.

Es geht darum, dass ich nicht genau weiß, wie die (ehemalige) each()-Funktion genau arbeitet und was man anstattdessen ab php 8 benutzt. Ich habe selber nur wenige Stellen in meiner Anwendung., wo ichs benutzt habe, aber eben doch eine Hand voll.

while(list($file,$info)=each($this->images)) {
...
}

Wenn ich das korrekt erinnere, geht while eine Liste von Arrays durch und each (oder list) teilt die Arrayelemente dann auf die beiden list-Parameter auf?

Würde ich das in php8 dann so umarbeiten?

foreach($this->images AS $file=>$info){

Und was ich auch nie ganz verstanden habe, ist, ob diese alte Funktion auch mehr als 2 Parameter hätte haben dürfen und wie man das dann ab php8 ersetzen würde. (Nur ganz allgemein gefragt, meine Handvoll Beispiele, in denen ichs ändern muss, haben alle nur 2 Parameter)

Gruß, Jörg

Hier mal die komplette Methode, aus der dieses beispiel entnommen ist.

function _putimages()
{
	$filter=($this->compress) ? '/Filter /FlateDecode ' : '';
	reset($this->images);
	while(list($file,$info)=each($this->images))
	{
		$this->_newobj();
		$this->images[$file]['n']=$this->n;
		$this->_out('<</Type /XObject');
		$this->_out('/Subtype /Image');
		$this->_out('/Width '.$info['w']);
		$this->_out('/Height '.$info['h']);
		if($info['cs']=='Indexed')
			$this->_out('/ColorSpace [/Indexed /DeviceRGB '.(strlen($info['pal'])/3-1).' '.($this->n+1).' 0 R]');
		else
		{
			$this->_out('/ColorSpace /'.$info['cs']);
			if($info['cs']=='DeviceCMYK')
				$this->_out('/Decode [1 0 1 0 1 0 1 0]');
		}
		$this->_out('/BitsPerComponent '.$info['bpc']);
		if(isset($info['f']))
			$this->_out('/Filter /'.$info['f']);
		if(isset($info['parms']))
			$this->_out($info['parms']);
		if(isset($info['trns']) && is_array($info['trns']))
		{
			$trns='';
			for($i=0;$i<count($info['trns']);$i++)
				$trns.=$info['trns'][$i].' '.$info['trns'][$i].' ';
			$this->_out('/Mask ['.$trns.']');
		}
		$this->_out('/Length '.strlen($info['data']).'>>');
		$this->_putstream($info['data']);
		unset($this->images[$file]['data']);
		$this->_out('endobj');
		//Palette
		if($info['cs']=='Indexed')
		{
			$this->_newobj();
			$pal=($this->compress) ? gzcompress($info['pal']) : $info['pal'];
			$this->_out('<<'.$filter.'/Length '.strlen($pal).'>>');
			$this->_putstream($pal);
			$this->_out('endobj');
		}
	}
}
  1. Hallo Jörg,

    warum zum grundgütigen Geier schaust Du nicht ins Handbuch?

    Das löst man heute mit foreach($array as $key => $value), da hast Du recht.

    Die each-Funktion hat aber eine Besonderheit, die foreach nicht hat: den Arrayzeiger. Mit reset($array) setzt man ihn auf das "erste" Element - was das erste Element ist, wird durch die zeitliche Reihenfolge bestimmt, in der die Elemente ins Array gekommen sind. Per Default steht der Arrayzeiger auf dem ersten Element, deswegen findest Du bei Dir vielleicht kein reset, wenn das Array nur ein einziges mal durchlaufen (durcheacht?) wird.

    each liefert zu dem Arrayelement, auf das der Arrayzeiger verweist, ein Array mit 4 Einträgen. Diese haben die Schlüssel 1, "value", 0 und "key", in dieser Reihenfolge. Das list-Sprachkonstrukt sucht in diesem Array nach den numerischen Schlüsseln und weist sie den entsprechenden Variablen zu, die list übergeben werden. Die erste Variable bekommt Index 0, die zweite den Index 1, und so weiter. Ingesamt ist das eine wüste Kopiererei und es ist ganz gut, dass das missbilligt wird.

    Das Ermitteln von $file und $info bildet foreach nach. Aber each setzt, bevor es zurückkehrt, den Arrayzeiger auch noch auf die nächste Position, während foreach den Arrayzeiger komplett ignoriert.

    Solange Du also eine Schleife hast, wo vor der Schleife der Arrayzeiger mit reset zurückgesetzt wird, und in/nach der Schleife nicht irgendwas passiert, was Bezug auf den Arrayzeiger nimmt, kannst Du es durch foreach ersetzen. In der gezeigten Schleife sieht das ganz gut aus.

    Aber mit foreach kannst Du etwas machen, was mit list/each nicht geht: Die Infos im Images-Array verändern. In $info scheint ja jeweils ein Array mit Informationen zum Image zu stehen, und du musst ein 'n' hinzufügen und ein 'data' entfernen. Dafür kannst Du nicht $info verwenden, weil PHP diesen "copy on write" Mechanismus für Arrays hat, die einer anderen Variable zugewiesen werden.

    $a = [ 1, 2, 3]; // ein Array
    $b = $a;         // Array wird NICHT kopiert, $b zeigt auf das Array in $a
    
    $b[4] = 17;      // JETZT wird erstmal kopiert, dann die 17 ergänzt
    
    // Neuer Versuch, mit Referenz
    $b &= $a;        // Array wird NICHT kopiert, $b zeigt auf das Array in $a
    
    $b[4] = 17;      // $b ist eine Referenz, es wird NICHT kopiert, 
                     // $a und $b zeigen weiterhin auf's gleiche Array.
    

    Der list($key, $info) = each($images) Mechanismus erzeugt keine Referenz, deswegen führen Änderungen an $info dazu, dass eine Kopie entsteht und Du musst mit $images[$key] rumfummeln.

    Mit foreach dagegen geht es so:

    foreach ($this->images as $file => &$info)
    {
       $info['n'] = $this->n;
       ...
    }
    

    Siehst Du das & hinter dem =>? Damit erzeugt die foreach-Schleife eine Referenz auf den Array-Inhalt und Änderungen sind möglich.

    Achso, $file brauchst Du dann ja gar nicht mehr 😀. Damit reicht

    foreach ($this->images as &$info)   // <-- REFERENZ!!!
    {
       $info['n'] = $this->n;
       ...
    }
    

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Hallo Rolf,

      warum zum grundgütigen Geier schaust Du nicht ins Handbuch?

      Hast Du selber mal reingeschaut? Falls ja, findest Du die Erklärung dort wirklich verständlich und ausreichend?

      Viele Dank auf jeden Fall für Deine Erklärungen zum Thema, die finde ich deutlich besser und verständlicher. 👍

      Jörg

      1. Hallo Forum,

        Ab php8 wird ja die count-Funktion nur noch bei auch tatsächlich "countable" Elementen (Arrays, countable objects) akzeptiert, ansonsten wirft sie einen Fehler raus.

        Habe mir nun überlegt, weil ich count() sooo oft benutzt habe und count() eigentlich immer in Zusammenhang mit Arrays nutze, die entweder mit einem oder mehreren Elementen gefüllt sind oder eben leer, d.h. ggf. auch nicht vorhanden sind, die php-count-Funktion gegen eine eigene Funktion countMyFunct() auszutauschen.

        Spricht da etwas gegen, was ich womöglich vergesswen haben könnte?

            function countMyFunct($value) {
             if (!is_array($value)) {
                 return 0;
             } else {
                 $wert = count($value);
                 return $wert;
             }
            }
        

        Und Zusatzfrage:

        Wäre meine Regex zum Auffinden der php-counts() korrekt oder geht das besser?

        ~\scount\s*\(\s*\$~
        

        Jörg

        1. Hallo,

          Ab php8 wird ja die count-Funktion nur noch bei auch tatsächlich "countable" Elementen (Arrays, countable objects) akzeptiert, ansonsten wirft sie einen Fehler raus.

          das mag stimmen, ich habe es jetzt weder praktisch noch theoretisch verifiziert. Aber ich wäre auch nie auf die Idee gekommen, count() für etwas anderes als ein Array zu benutzen. Was sollte dann das Ergebnis sein? Welche Semantik würde dahinter stehen?

          Habe mir nun überlegt, weil ich count() sooo oft benutzt habe und count() eigentlich immer in Zusammenhang mit Arrays nutze, die entweder mit einem oder mehreren Elementen gefüllt sind oder eben leer, d.h. ggf. auch nicht vorhanden sind, die php-count-Funktion gegen eine eigene Funktion countMyFunct() auszutauschen.

          Wofür? Du schreibst selbst, dass du count() "eigentlich immer" auf Arrays anwendest. Welchen Mehrwert hätte also eine solche Funktion?

          Spricht da etwas gegen, was ich womöglich vergesswen haben könnte?

          Nichts dagegen, aber IMO auch nichts dafür.

          Und Zusatzfrage:

          Wäre meine Regex zum Auffinden der php-counts() korrekt oder geht das besser?

          ~\scount\s*\(\s*\$~
          

          Du setzt voraus, dass vor dem Bezeichner count ein Whitespace steht. Was ist mit sowas:

          foo($theArray,count($theArray));
          

          Also am Anfang besser \b (Word boundary) anstatt \s nehmen. Und das optionale Whitespace am Ende deines Musters ist keine Kunst, das kann weg.

          Einen schönen Tag noch
           Martin

          --
          In Massachusetts gilt heute noch per Gesetz, dass man nicht zu Bett gehen darf, ohne vorher zu baden.
          Außerdem verbietet ein anderes Gesetz das Baden am Sonntag.
          Darf man also in der Nacht von Sonntag auf Montag nicht ins Bett?
          1. Hallo Martin,

            Ab php8 wird ja die count-Funktion nur noch bei auch tatsächlich "countable" Elementen (Arrays, countable objects) akzeptiert, ansonsten wirft sie einen Fehler raus.

            ich wäre auch nie auf die Idee gekommen, count() für etwas anderes als ein Array zu benutzen. Was sollte dann das Ergebnis sein? Welche Semantik würde dahinter stehen?

            Ich fülle ab und an Arrays mit Elementen, falls vorhanden. Aber ohne zuvor das Array zu definieren, also:

            $myArray[] = $bla;

            wobei $bla z.b. aus einer db kommt und ggf. leer ist.

            Und genau hier meckert php8, wenn ich dann einen count übers Array laufen lassen will.

            Wäre meine Regex zum Auffinden der php-counts() korrekt oder geht das besser?

            ~\scount\s*\(\s*\$~
            

            Du setzt voraus, dass vor dem Bezeichner count ein Whitespace steht.

            Was ist mit sowas:

            foo($theArray,count($theArray));
            

            Stimmt. Gut, dass ich gerfagt habe.

            Also am Anfang besser \b (Word boundary) anstatt \s nehmen. Und das optionale Whitespace am Ende deines Musters ist keine Kunst, das kann weg.

            \b ist gut.

            Das Dollarzeichen sollte gar nicht für das Ende des Musters stehen, daher auch escaped. ich suche nach dem Dollarzeichen 😉

            Jörg

        2. Hallo Jörg,

          es ist schon länger eine Warning, und wer Warnungen ignoriert, rennt irgendwann vor die Wand. Weil es jetzt ein fatal error ist, kann man es auch nicht mit einem Error-Handler abfangen.

          Dass der Sanierungsbedarf tatsächlich anderswo besteht, weißt Du aber, ja? Du prüfst mit count, ob Du Ergebnisse hast. Ein Array hast Du durch deine Hilfsfunktion immer noch nicht, d.h. du musst an der Stelle ohnehin den Code überspringen, der das Array verarbeiten will.

          Ich würde wetten, dass Du an all den Stellen, wo es crasht, eine Abfrage
          if (count($array) > 0) hast.

          Also solltest Du an diesen Stellen generell die Abfrage klarer formulieren.

          if (count($dubiousArray) > 0) 
          {
             // process dubious array
          }
          
          if (is_array($dubiousArray) && count($dubiousArray) > 0) 
          {
             // process dubious array
          }
          

          Natürlich kann man diese Bedingung noch in eine Funktion kapseln. Diese sollte dann aber so ähnlich wie "is_data_in_array" oder "array_has_data" heißen. Das führt zu Code, der sich wie ein Satz liest:

          if (is_data_in_array($dubiousArray)) 
          {
             // process dubious array
          }
          
          if (array_has_data($dubiousArray)) 
          {
             // process dubious array
          }
          

          Das ist allerdings nicht zwingend nötig. Denn oft genug hat man an solchen Stellen eine foreach-Schleife, die bei einem leeren $dubiousArray ohnehin nichts tut. Das muss man sich natürlich Stelle für Stelle angucken.

          Die Funktion selbst würde ich dann genauso schreiben:

          function is_data_in_array($dubiousArray) : int
          {
             return is_array($dubiousArray) && count($dubiousArray) > 0;
          }
          

          Eine generische Count-Kapsel würde ich zu vermeiden suchen.

          Rolf

          --
          sumpsi - posui - obstruxi