Es ist ein Model und es sieht gut aus – Der Observer

Zum Abschluss dieser Woche werden wir heute noch schnell das Observer-Pattern verbauen und dazu das passende Interface SplSubject implementieren. In dem Zusammenhang findet dann auch die Klasse SplObjectStorage eine sinnvolle Verwendung.

Zuerst erweitern wir das Interface Custom_Model_Interface um SplSubject und ein paar eigene Methoden.

<?php
interface Custom_Model_Interface
    extends ArrayAccess,
            Iterator,
            Countable,
            SplSubject
{
  public function mark();
  public function modified();
  public function cleanModified();
  public function modifiedKeys();
}

Anschliessend kümmern wir uns um die Klassen Custom_Model und bauen alles Nötige ein.

<?php
class Custom_Model implements Custom_Model_Interface
{
  // ...
  protected $_observers;
  protected $_modified;

  public function attach(SplObserver $observer) {}
  public function detach(SplObserver $observer) {}
  public function notify() {}

  public function mark() {}
  public function modified() {}
  public function cleanModified() {}
  public function modifiedKeys() {}
  // ...
}

So weit, so gut! Die ersten drei Methoden attach(), detach() und notify() werden durch das Interface SplSubject vorgeschrieben und sind die Schnittstelle, um Observer am Objekt zu registrieren, sie zu entfernen oder zu benachrichtigen.

Mit den vier weiteren können wir den Status des Objektes setzen oder kontrollieren. Die beiden protected Eigenschaften enthalten eine Liste von Typ SplObjectStorage für die Observer und ein einfaches Array für den jeweiligen Status eines Feldes.

Bevor wir das verwenden können, müssen wir es im Konstruktor initialisieren.

public final function __construct()
{
  $this->_observers = new SplObjectStorage();
  $this->cleanModified();
  // ...
}

Der Vorteil von SplObjectStorage ist der, dass jedes Objekt nur einmal darin vorkommen kann und wir uns um die Steuerung keine Gedanken machen müssen. Das macht dann auch das Hinzufügen und Entfernen eines Observers sehr einfach.

public function attach(SplObserver $observer)
{
  $this->_observers->attach($observer);
  return $this;
}

public function detach(SplObserver $observer)
{
  $this->_observers->detach($observer);
  return $this;
}

Mir zaubert das immer ein breites Grinsen ins Gesicht, wenn eine Aufgabe mit so wenig Code gelöst werden kann und der Doc-Block teilweise länger ist als die Funktion.

Aber nun wieder zum Thema. Bevor wir uns an die wirklich einfache Benachrichtigung machen, werde ich noch schnell auf die Sache mit der Statuskontrolle eingehen. Eine Aktion, z.B. _set() setzt den “modified”-Status eines Feldes, so dass jeder Schreibzugriff auf das Modell erkennbar wird.

Dadurch haben wir später die Möglichkeit, nur zu benachrichtigen, wenn es nötig ist oder auch nur die Felder zu speichern, die sich verändert haben.

Um eine Änderung bekannt zu machen verwenden wir mark().

public function mark($key)
{
  if($this->keyExists($key)) {
    $this->_modified[$this->_mapKey($key)] = true;
  }
  return $this;
}

Die Methode erwartet den Namen eines existierenden Feldes und setzt ein Flag im Array.

Um den Status des Objektes oder eines einzelnen Feldes abfragen zu können haben wir dann modified().

public function modified($key = null)
{
  if(null !== $key) {
    return isset($this->_modified[$this->_mapKey($key)]);
  }
  return count($this->_modified) > 0;
}

Durch die Angabe des Parameters $key wird ein Feld abgefragt, ansonsten der Gesamtstatus. Nach dem selben Prinzip funktioniert dann auch cleanModified(), um den Status wieder zurück zu setzen.

public function cleanModified($key = null)
{
  if(null != $key) {
    unset($this->_modified[$this->_mapKey($key)]);
  } else {
    $this->_modified = array();
  }
  return $this;
}

Falls wir eine Liste der veränderten Felder brauchen, dann ist dafür modifiedKeys() vorgesehen.

public function modifiedKeys()
{
  $keys = array();

  foreach($this->_keys as $key) {
    if($this->modified($key)) {
      $keys[] = $key;
    }
  }

  return $keys;
}

Nachdem wir das alles durch haben, können wir uns nun notify() widmen, bevor wir unser Werk an ein paar Beispielen testen können.

public function notify()
{
  if($this->modified()) {
    foreach($this->_observers as $observer) {
      $observer->update($this);
    }
  }
  $this->cleanModified();
  return $this;
}

Die Methode macht im Prinzip nichts weiter, als bei allen in der Liste befindlichen Observer-Objekten update() aufzurufen und sich selbst als Parameter mitzugeben. Dadurch weiß die Observerklasse gleich, welches Objekt sich verändert hat.

Das folgende Beispiel zeigt einen einfachen Beobachter, der nur den Klassennamen von $subject ausgibt und eine Liste der geänderten Felder, wenn das Objekt vom Typ Custom_Model ist.

class myObserver implements SplObserver
{
  public function update(SplSubject $subject)
  {
    echo get_class($subject) . ' hat sich verändert<br/>';
    if($subject instanceof Custom_Model) {
      print_r($subject->modifiedKeys());
    }
  }
}

$model = new Person();
$model->attach(new myObserver());
$model->setId(42)
      ->setVorname('Bart');

$model->notify();

Das interessante an dem Observer-Muster ist die lose Kopplung der Objekte und die Tatsache, dass ein Observer beliebig viele Subjekte beobachten kann und umgekehrt ein Subjekt beliebig viele Observer haben kann.

So, das war es dann auch schon für diese Woche und zu diesem Thema. Wie ich einst schon sagte, ist es ein sehr ergiebiges Thema, weil sich viele Techniken in sinnvoller Verbindung anwenden lassen. Bislang haben wir trotzdem erst nur einen Datenklumpen mit etwas Dekoration.

Richtig interessant wird es dann, wenn wir die Models über Mapper-Klassen laden und speichern können, aber das wird dann evtl. das Thema einer neuen Serie…

3 Kommentare

  1. Pingback: Es ist ein Model und es sieht gut aus « ebene7

  2. Pingback: Es ist ein Model und es sieht gut aus – Iterator « ebene7

  3. Guten Abend,

    vielen Dank für die echt guten Artikel. Sie haben mir besonders das DataMapper Pattern noch einmal erheblich näher gebracht, wo ich mir vorher ziemlich unsicher war, was die Umsetzung anging. Weiter so! :)

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