“Walkable” Iterator

In PHP haben Entwickler seit Version 5 die Möglichkeit, den Zugriff auf Datenstrukturen durch eigene oder bereits in der SPL vorhandene Iteratoren zu kontrollieren. Das ist einfach und hat sicherlich auch schon jeder einmal gemacht.

Damit ist dann zumindest der eine Teil, die Datenstruktur, für sich gekapselt. Was ist aber mit dem Teil, den wir wahrscheinlich häufiger verwenden werden? Wir nutzen Iteratoren in Schleifen und machen irgendwas mit den Elementen.

<?php
foreach($list as $key => $item) {
  $item->doSomething();
}

// oder
$values = array();
foreach($list as $key => $item) {
  $values[] = $item->getSomething();
}

In verschiedenen anderen Sprachen, Java, JavaScript etc., sieht man häufig, dass dort auch die Scheifen gekapselt sind. Warum probieren wir das nicht auch mal in PHP?

Ich werde das Beipiel bewusst einfach halten und wie gewohnt auf einige Fehlerbehandlungen verzichten, um den Code übersichtlich zu halten.

Als erstes erstellen wir eine Klasse “WalkableIterator” und lassen diese von ArrayIterator erben. Die beiden Methoden “each” und “walk” bilden später unsere beiden Beispiele ab.

<?php
class WalkableIterator extends ArrayIterator {
  public function each($method) {}
  public function walk($callback) {}
}

Mit each() können wir eine bestimmte Methode auf jedem Element in unserem Iterator aufrufen. Dafür müssten hier natürlich diverse Fehlerbehandlungen rein was passieren soll, wenn das Element kein Objekt ist oder die Methode nicht existiert oder diese gar eine Exception wirft.

<?php
class WalkableIterator extends ArrayIterator {
  public function each($method) {
    $args = func_get_args();
    $method = array_shift($args);
    foreach($this as $item) {
      call_user_func_array( array($item, $method), $args );
    }
  }
  // ...
}

Das ganze ist denkbar einfach: Die Methode nimmt als ersten Pflichtparameter den Methodennamen entgegen, die auf allen Elementen aufgerufen werden soll und der Rest bleibt flexibel und wird einfach nur durchgereicht.

Die Implementierung von walk() wird sogar noch einfacher, da hier keine Parameter weitergegeben werden müssen. Im produktiven Einsatz müssten auch hier noch die üblichen Fehlerbehandlungen rein.

<?php
class WalkableIterator extends ArrayIterator {
  // ...
  public function walk($callback) {
    foreach($this as $item) {
      call_user_func( $callback, $item );
    }
  }
}

Soweit, so einfach… Was können wir jetzt damit machen? Bauen wir ein kleines Beispiel.

<?php
// Testklasse
class Person {
  private $name;
  public function __construct($name = 'unnamed') {
    $this->setName($name);
  }
  public function getName() {
    return $this->name;
  }
  public function setName($name) {
    $this->name = $name;
  }
}

// Testarray mit Objekten befüllen
$a = array();
for($i=1; $i <= 10; $i++) {
  $a[] = new Person('Person ' . $i);
}

// Iterator erstellen und Namen ausgeben
$it = new WalkableIterator($a);

foreach($it as $item) {
  echo $item->getName() . '<br/>';
}

// Person 1
// ...
// Person 10

Wollten wir nun allen Personenobjekten einen Namen geben, so könnten wir dafür each() verwenden.

<?php
$it->each('setName', 'PHP-Nerd');

// PHP-Nerd
// ...
// PHP-Nerd

Wollen wir unsere Personen dann vielleicht doch wieder individueller benennen, so könnten wird das mit walk() und einem Callback machen.

$callback = function(Person $person) {
  static $count = 1;
  $person->setName( $person->getName() . ' ' . $count++ );
};

$it->walk($callback);
// PHP-Nerd 1
// ...
// PHP-Nerd 10

“Was bringt uns das und warum so kompliziert?”, ist in dem Zusammenhang eine oft gehörte Frage.

Zunächst ist die Implementierung, wie das Beispiel sicher beweist, nicht kompliziert.

Die Vorteile sind kürzere Aufrufe im Code, weil wir keine Schleifen mehr schreiben müssen und durch die Kapselung verbessert sich auch die Wart- und Testbarkeit. Der Code existiert nur an einer Stelle und funktioniert überall nach dem selben Muster.

Ein Kommentar

  1. Warum eigentlich nicht? Sieht sehr vernünftig aus.
    Netter Beitrag :)

Schlagwörter: Adapter, Amazon, Animation, Annotations, Anonyme Klasse, Ant, Apache, API, Array, ArrayAccess, Attachment, AutoLoader, Bedienung, Bedingung, Benchmark, Bildbearbeitung, BOM, Bootstrap, Bot, Byte Order Mark, Callback, CamelCase, Canvas, Captcha, Cheatsheet, CLI, Closure, Cloud, CodeSniffer, Community, Comparator, Contest, Controller, Converter, CouchDB, Countable, Cronjob, CSV, CustomLibrary, Custom_Model, Data Mapper, Datei, Datenbank, Datenstruktur, Datentypen, Dating, Decorator, Dekorierer, Design Patterns, Dump, Duplikat, each, Eclipse, Entwicklung, Entwurfsmuster, Enum, Erweiterung, Eventhandling, Exception-Handling, Extension, Factory, Fehler, Flash, Foreach, Formatierung, Formular, Funktion, Futon, Header, HTML5, HTTP, IDE, If, Implementierung, InnoDB, Interceptor, Interface, isset, Iterator, Java, JavaScript, jQuery, Konfiguration, Konsole, Kontrollstruktur, kopieren, Late Static Binding, Layout, Linux, Listeners, Logging, Löschen, Magento, Magic Methods, Marketing, Methode, Model, MVC, MySQL, NetBeans, Objekt, Observable, Observer, OOP, Operator, Parameter, Partnersuche, Performance, PHP, phpMyAdmin, PHPUnit, Plugin, Proxy, Qualitätssicherung, Query, Reflection, Request, Response, Rest-API, Rockstar, Routing, S3, Samba, Scheifen, Schleife, Schutz, Secure Shell, Selbstreferenz, Shop, Sicherheit, Sicherung, Singleton Pattern, SOAP, Sortierung, Sourcecode, Spam, Speicherproblem, Spickzettel, SPL, SSH, Statement, Stellvertreter, Strategy Pattern, Stream, String, Sun VirtualBox, Support, Switch, Symfony, Symfony2, Symfony Live, Tag, Template, Template Method, Ternär Operator, Testing, Thumbnail, Tool, Tour, Twig, Type-Cast, Umwandlung, Underscore, unset, Vererbung, Verzweigung, Video, Videospiel, Virtualisierung, Visitor Pattern, Vorschaubild, walk, Webserver, Webservice, Weiterleitung, Wrapper, Youtube, Zeitsteuerung, Zend Framework, Zend_Cloud, Zend_CodeGenerator, Zend_Http_Client, Zend_Service, Zugriffsmethode