1. Januar 2013 | 1 Comment Hallo zusammen, ich hoffe ihr seid alle gut in das neue Jahr gerutscht und bereit für neue Einträge im Web-Developer-Blog! Heute möchte ich über ein paar der neuen Features von PHP 5.5 schreiben, welches sich derzeit in der zweiten Alpha Version befindet – Unter anderem geht es Heute um… Generatoren Support für „list“ Funktion in foreach-Schleifen Neuer „finally“ Code-Block (für Exceptions) Generatoren (und Co-Routinen) Dieses neue Feature erlaubt es uns mit Hilfe von wenig Code eigene Iteratoren zu entwickeln. Das kann uns in bestimmten Situationen viel Code einsparen, setzt aber eine ziemlich abstrakte Denkweise voraus… Ich werde hier das Beispiel aus dem PHP Wiki benutzen um zu erklären und zeigen worum es geht. Stellen wir uns folgende Situation vor: Wir möchten eine eigene Funktion (ähnlich file) implementieren: function getLinesFromFile($fileName) { if (!$fileHandle = fopen($fileName, 'r')) { return; } $lines = []; while (false !== $line = fgets($fileHandle)) { $lines[] = $line; } fclose($fileHandle); return $lines; } $lines = getLinesFromFile($fileName); foreach ($lines as $line) { // do something with $line } Diese implementation birgt folgende Nachteile: Wir laden stets die komplette Datei in den Speicher (und abeiten streng prozedural anstatt Objekt-Orientiert). Allerdings muss man zugeben: Es ist ein kurzes Stück Quelltext und relativ leicht zu verstehen. Eine Objekt-Orientierte Lösung (inklusive Nutzung des Iterator Interfaces) schaut in etwa so aus: class LineIterator implements Iterator { protected $fileHandle; protected $line; protected $i; public function __construct($fileName) { if (!$this->fileHandle = fopen($fileName, 'r')) { throw new RuntimeException('Couldn\'t open file "' . $fileName . '"'); } } public function rewind() { fseek($this->fileHandle, 0); $this->line = fgets($this->fileHandle); $this->i = 0; } public function valid() { return false !== $this->line; } public function current() { return $this->line; } public function key() { return $this->i; } public function next() { if (false !== $this->line) { $this->line = fgets($this->fileHandle); $this->i++; } } public function __destruct() { fclose($this->fileHandle); } } $lines = new LineIterator($fileName); foreach ($lines as $line) { // do something with $line } Wir haben also folgendes getan: Die grundlegende Funktion wurde sauber in eine Klasse ausgelagert welche das Iterator Interface implementiert… Dadurch können wir (wie im Quelltext zu sehen) mit Hilfe einer nativen foreach Schleife über die Zeilen der Datei iterieren. Das ist mit Abstand die sauberste Lösung… Doch blöderweise ist durch das genutzte Interface einiges an Boilerplate-Methoden hinzugekommen, die den Code um viele Zeilen „aufblähen“ (rewind, valid, current, next, …). Bühne Frei für Generatoren und das yield Statement: function getLinesFromFile($fileName) { if (!$fileHandle = fopen($fileName, 'r')) { return; } while (false !== $line = fgets($fileHandle)) { yield $line; } fclose($fileHandle); } $lines = getLinesFromFile($fileName); foreach ($lines as $line) { // do something with $line } Wie wir sehen, handelt es sich um einen sehr ähnlichen Aufbau wie im ersten Code-Beispiel… Doch die Funktionsweise ist dieses Mal sehr anders: Die Funktion getLinesFromFile() erzeugt bei Aufruf eine Instanz der Generator Klasse, welche wiederum das Iterator Interface implementiert. Sobald die Funktion gerufen wird, erstellt PHP eine reine Klasseninstanz – Es wird kein weiterer Code ausgeführt. Sobald die Iterator::next() Methode aufgerufen wird (z.B. beim durchlauf der foreach-Schleife), wird der Code weiter ausgeführt, bis das yield Statement erreicht wird. Das geht so lange weiter, bis die Funktion ihr Ende erreicht. Es wird also zur Laufzeit zwischen zwei Code-Bereichen hin- und her gesprungen (daher auch der Name „Co-Routine“)… Wie gesagt, das ganze ist relativ abstrakt. Doch damit nicht genug! Das yield Statement kann auch andersrum benutzt werden: Es kann Parameter entgegennehmen – Doch das habe ich mir ehrlich gesagt noch nicht so genau angesehen… Ein Beispiel kann hier gefunden werden: Beispiel eines streamingXML parsers. Das Thema bietet also noch sehr viel mehr an Features, doch das ganze kann und möchte ich erst weiter vertiefen sobald PHP 5.5 final erscheint – Ich denke ich werde dann zu den Generatoren einen eigenen Blog-Eintrag veröffentlichen. Übrigens: Das yield Statement und Generatoren existieren bereits in Javascript (ab 1.7)! Wie werden Generatoren erkannt? PHP wird automatisch jede Funktion oder Klasse als „Generator“ erkennen, sobald im Quelltext yield auftaucht. Die genaue Funktionsweise der Generator Klasse sowie die yield Syntax (mit ein paar Beispielen) lässt sich hier finden: „Generators: Basic behavior„. Und nun weiter im Text… Support für „list“ Funktion in foreach-Schleifen Ich schätze am einfachsten lässt sich dieses Feature mit Hilfe eines Quelltextes erklären: $users = array( array('Max', 'Mustermann'), array('Admin', 'Istrator'), ); // Vor PHP 5.5 foreach ($users as $user) { list($first_name, $last_name) = $user; echo 'Vorname: ' . $first_name . ', Nachname: ' . $last_name; } // Ab PHP 5.5 foreach ($users as list($first_name, $last_name)) { echo 'Vorname: ' . $first_name . ', Nachname: ' . $last_name; } Wie wir sehen lässt sich der Inhalt des aktuellen Array-Elements direkt mit Hilfe der list() Funktion aufsplitten. Das spart uns die, in diesem Fall, überflüssige „$user“ Variable. Neuer “finally” Code-Block (für Exceptions) Der „finally“ Code-Block für Exceptions ist im Grunde nichts neues – In vielen Programmiersprachen existiert diese Anweisung bereits seit längerer Zeit. Der Quelltext in einem „finally“ Block wird immer ausgeführt, egal ob eine Exception gefangen wird oder der „try“ Block erfolgreich durchläuft… Stellen wir uns folgende Situation vor: $fileHandle = fopen($fileName, 'r'); try { einige_funktionen($fileHandle); // ... } catch (Exception $e) { fclose($fileHandle); throw $e; } fclose($fileHandle); Hier haben wir die fclose() Funktion doppelt implementiert, damit in allen Fällen die Datei wieder ordnungsgemäß geschlossen wird. Mit hilfe unseres „finally“ Blocks kann dieser Code nun folgendermaßen umgeschrieben werden: $fileHandle = fopen($fileName, 'r'); try { einige_funktionen($fileHandle); // ... } finally { fclose($fileHandle); } Wo ist der „catch“-Block und wieso wird die Exception nicht weiter geworfen…? Wie bereits geschrieben wird der „finally“ Code-Block IMMER ausgeführt – Dadurch kann (in diesem Beispiel) der „catch“-Block entfallen: Die Exception wird, nachdem der Code aus dem „finally“-Block ausgeführt wurde, wie vorher geworfen. Der „finally“ Code-Block wird also immer vor der letzten Anweisung (Beispielsweise „return“) ausgeführt (Beispiel vom PHP Wiki): try { return 2; } finally { echo "this will be called\n"; } //this will never be called echo "you can not see me"; Das Ergebnis dieses Quelltextes wäre folgendes this will be called //return int(2) Etwas komplexer wird es bei verschachtelten try/catch/finally-Blöcken… function foo ($a) { try { echo "1"; try { echo "2"; throw new Exception("ex"); } catch (Exception $e) { echo "3"; } finally { echo "4"; throw new Exception("ex"); } } catch (Exception $e) { echo "3"; } finally { echo "2"; } return 1; } var_dump(foo("para")); Hier werden wir folgendes Ergebnis erhalten 123432int(1) Schauen wir uns den Quelltext genauer an und vergleichen ihn mit der erhaltenen Ausgabe, macht alles Sinn… Auch wenn es auf den ersten Blick eher verwirrt. Dieser neue Code-Block kann uns also dabei helfen doppelten Code zu verringern und gleichzeitig „nachfolge“-Code (z.B. zum schließen von Dateien oder Datenbank-Verbindungen) zuverlässig auszuführen. Und mit diesen Worten möchte ich den ersten Eintrag für 2013 auch schon abschließen! Euch da draußen wünsche ich ein gutes Jahr und freue mich auf viele Leser und Leserinnen 🙂 In diesem Sinne… „Ein frohes neues Jahr und happy Coding!“