Implementieren einer einfachen Plugin-Schnittstelle

Heute geht es darum, wie wir uns eine einfache Plugin-Schnittstelle für unsere Objekte bauen können. Als Beispiel, wie sollte es auch anders sein, eignet sich unsere Custom_Model-Klasse aus meinen vergangenen Artikeln.

Und schon wieder die Frage, was kann unsere Klasse dann mehr? Oder warum machen wir das?

Oftmals haben wir Objekte, die eine gewisse Menge identischer Methoden benötigen, jedoch nicht aus einer Vererbungslinie stammen.

Durch die Plugins können wir dann quasi Funktionen an unsere Objekte dran kleben und nutzen. Diesen Code müssen wir nur einmal schreiben, haben auch nur eine Stelle im Code zu pflegen und können dadurch sicherstellen, dass alles gleich funktioniert.

So erweitern wir nun also zuerst wie gewohnt unser Custom_Model um ein paar neue Methoden und Eigenschaften.

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

  public function registerPlugin() {}
  public function unregisterPlugin() {}
  // ...
}

Und das Interface für die Plugins.

<?php
interface Custom_Model_Plugin_Interface {}

Beim Speichern der Plugins kommt wieder die Klasse SplObjectStorage zum Einsatz, welche wir ja schon bei der Implementierung des Obeservers verwendet haben. Demnach ist es auch nicht überraschend, dass die Registrierung an die Abmeldung der Plugins sehr einfach wird.

public function registerPlugin(Custom_Model_Plugin_Interface $plugin)
{
  $this->_plugins->attach($plugin);
  return $this;
}

public function unregisterPlugin(Custom_Model_Plugin_Interface $plugin)
{
  $this->_plugins->detach($plugin);
  return $this;
}

Die Models müssen das Interface Custom_Model_Plugin_Interface implementieren. Dieses Interface ist lediglich ein Flag- oder Marker-Interface, d.h. es schreibt keine Methoden vor.

Jetzt noch eine kleine Änderung an der Methode __call() und wir können schon mit dem Beispiel experimentieren.

public function __call($method, $args)
{
  // Magie für die Getter & Setter

  foreach($this->_plugins as $plugin) {
    if(method_exists($plugin, $method)) {
      array_unshift($args, $this);
      return call_user_func_array(array($plugin, $method), $args);
    }
  }

  // Fehlerbehandlung
}

Durch __call() können wir unsere Plugin-Methoden so nutzen, als gehörten sie direkt zum Objekt. Die Art wie die Plugins verwendet werden  hat Vor-, aber auch Nachteile.

Die Liste der Plugins wird der Reihe nach durchlaufen und die erstbeste passende Methode wird aufgerufen. Auch die Getter oder Setter könnten wir nicht mehr überschreiben. Wenn das alles jedoch gewollt ist, dann lässt sich das aber auch mit wenigen Handgriffen erweitern.

Nun aber mal was praktisches zum Anschauen: Angenommen wir haben eine Model-Klasse mit Abhängigkeit zu anderen Objekten.

<?php
class Book extends Custom_Model
{
  protected $_id;
  protected $_title;
  protected $_ownerId;
}

Nun wollen wir nicht nur die ID des Besitzers erfahren, sondern auch das Objekt das bekommen können. Dafür spendieren wir ein Plugin.

<?php
class GetOwnerPlugin implements Custom_Model_Plugin_Interface
{
  public function getOwner($sender)
  {
    $id = $sender->getOwnerId();

    // lade User-Objekt $owner anhand der $id

    return $owner;
  }
}

Auf die Fehlerbehandlung habe ich an der Stelle mal verzichtet, sollte jedoch unbedingt stattfinden.

<?php
$book = new Book();
$book->registerPlugin(new GetOwnerPlugin());
$book->setOwnerId(42);
$owner = $book->getOwner();

Spätestens hier fällt sicher nicht nur mir auf, dass das Konzept noch Lücken hat. Theoretisch kann es sein, dass zu verschiedenen IDs ein User-Objekt nachgeladen werden soll (getAuthor(), getModifier(), getLastVisitor()…) und dann bräuchten wir sämtliche Methoden in unserem Plugin oder verschiedene Plugins.

Dann hätten wir aber auch wieder mehrfachen Code und nichts gewonnen. Daher werde ich die Pluginschnittstelle noch etwas aufbohren und das Ergebnis dann in einem meiner nächsten Artikel vorstellen.

Ein Kommentar

  1. Super Artikel um sich das ganze mal vorzustellen!
    Ich erwarte mit Spannung die versprochene Fortsetzung ;) .

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