PHP Entwurfsmuster :: Singleton

Wie schon in unserem Artikel vom Niemand zum Hacker geschrieben, arbeiten wir seit geraumer Zeit mit Entwurfsmustern. Seit der Version 5, bietet PHP nahezu alle OO-Fähigkeiten, wie die etablierten Objekorientiereten Sprachen Java oder C++. Der Anwendung von Entwurfsmustern steht so also nichts mehr im Wege. Ich werde hier nicht darauf eingehen was Entwurfsmuster sind, dass werden wir in kürze in einem eigenen Blogbeitrag nachholen.

Das Singleton. Das Singletonmuster gewährleistet, dass immer nur eine Instanz einer Klasse zur Laufzeit existiert.

Das erste Entwurfsmuster, dass ich vorstellen möchte ist zugleich auch das einfachste. Das Singleton. Das Singletonmuster gewährleistet, dass immer nur eine Instanz einer Klasse zur Laufzeit existiert. Mit einem Singletonmuster kann somit der Zugriff auf ein Objekt kontrolliert werden. Ich habe eine kurze Beispielimplementierung geschrieben um das Muster, daran, zu erklären. In den meisten Webanwendungen, macht es wenig Sinn mehrere Verbindungen zur gleichen Datenbank zu halten. Es gibt jedoch ohne das Singleton Muster nicht viele Alternativen dies sicherzustellen. Die einzige die mir jetzt spontan einfällt ist, ein Datenbankobjekt Global zur Verfügung zu stellen und allen Klassen die eine Datenbankverbindungen benötigen im Konstruktor zu übergeben. Nun aber zum Beispiel. Ein Linkobjekt ließt einen Hyperlink aus einer Linkdatebank aus und gibt diesen zurück. Auf die in der Datenbank abgelegten Links kann anhand einer ID zugegriffen werden. Als erstes brauchen wir eine Verbindung zur Datenbank. Hierfür schreiben wir ein Datenbankobjekt, welches eine Verbindung zur Datenbank kapselt und sicherstellt, dass immer nur eine Instanz der Klasse zur Laufzeit existiert.

iDatabaseObject.class.php

<?php
  class MySQLiException extends Exception {
  }
  class MySQLiConnectionException extends MySQLiException {
  }
 
  class iDatabaseObject extends MySQLi
  {
    private static $singletonInstance = null;
 
    private function __construct($server, $user, $password, $database)
    {
      parent::__construct($server, $user, $password, $database);
 
        if (mysqli_connect_errno())
        {
          throw new MySQLiConnectionException(mysqli_connect_error(), mysqli_connect_errno());
        }
    }
 
     public static function getSingletonInstance()
    {
      if(self::$singletonInstance == null)
      {
         self::$singletonInstance = new iDatabaseObject('server', 'user', 'password', 'database');
      }
      return self::$singletonInstance;
    }
 
    public function __clone()
    {
    }
 
    public function __destruct()
    {
      $this->close();
    }
    public function query($query) 
    {
      return $result = parent::query($query);
    }
 
    public function getResultRowByID($ID,$query)
    { 
      $result = array();
      $stmt = $this->prepare($query);
      $stmt->bind_param("i", $ID);
      $stmt->execute();
      $stmt->bind_result($result);
      $stmt->fetch();
      $stmt->close();
 
      return $result;
    }
  }
?>

Das Beispiel verwendet die MySQLi Erweiterung von PHP. Die Datenbankklasse erbt vom MySQLi Objekt, so können alle parent Methoden direkt auf dem Datenbankobjekt aufgerufen werden. Die geschützte Variable $singletonInstance kapselt später die Instanz des Objektes und wird mit null initialisiert. Der private Konstruktor bekommt bei Instanzierung des Datenbankobjektes, die Verbindungsdaten übergeben und ruft mit diesen den Konstruktor des parent (MySQLi Objekt) auf. Kommt es beim Aufbau der Verbindung zu einem Fehler wird eine Exception geworfen um dies zu signalisieren. mysqli_connect_error() liefert hierbei die Fehlerbeschreibung und mysqli_connect_errno() den Fehlercode.
Um das Datenbankobjekt verwenden zu können müssen wir noch eine Instanz erzeugen können, dies geht im Moment noch nicht, weil der Konstruktor private ist. Deshalb implementieren wir die statische und öffentliche Methode getSingletonInstance(). Der Trick hierbei ist, dass man diese Methode global mit

iDatabaseObject::getSingletonInstance()

aufrufen kann und da Sie eine Mehtode der Datenbankklasse ist, über Sie auf den privaten Konstruktor zugegriffen werden kann, um eine neue Instanz zu erzeugen. Die Methode prüft nun ob die statische Klassenvariable $singletonInstance schon eine Instanz der Klasse hält oder nicht. Wenn keine Instanz vorhanden ist, also $singletonInstance = null, wird ein neues Datenbankobjekt erzeugt und wenn bereits eine Instanz hinterlegt ist wird diese einfach zurückgegeben. Nun muss nur noch der __clone Interzeptor implementiert werden damit auch keine Kopie des Objektes erstellt werden kann. Es ist nun sichergestellt, dass immer nur eine Instanz zur Laufzeit existiert. Jetzt können wir damit beginnen die Klasse Link zu implementieren. Diese soll anhand einer übergebenen ID den zugehörigen Hyperlink aus der Datenbank auslesen und kapseln.

Link.class.php

class Link{
 
    protected $ID = '';
    protected $link = ''; 
    public function __construct($ID)
    {
      $this->ID = $ID;
      $this->link = iDatabaseObject::getSingletonInstance()->getResultRowByID($this->ID,'SELECT link FROM testtable WHERE id=?');
    }
 
    public function getLink()
    {
      return $this->link;
    }
 
    public function getID()
    {
      return $this->ID;
    }
 
    public function __toString()
    {
       $output = $this->getLink();
       return $output;
    }
}

Die Link Klasse ruft nun in Ihrem Konstruktor die statische Methode getSingletonInstance() der Datenbankklasse auf und bekommt eine Instanz zurück, auf welcher nun die Methode getResultRowByID($ID,$query), mit den Parametern der Link ID und des SQL Befehls, aufgerufen werden kann.

iDatabaseObject::getSingletonInstance()->getResultRowByID($this->ID,'SELECT link FROM testtable WHERE id=?')

getResultRowByID($ID,$query) liefert als Ergebnis einen String mit dem, der ID entsprechenden, Link zurück.

Um das ganze zu testen, legen wir eine neue Datei an.

engine.php

<?php
function __autoload($className)
  {
    require_once $className.'.class.php';
  }
 
$link1 = new Link('1');
$link2 = new Link('2');
print $link1;
print $link2;
?>

Als erstes müssen wir hier sicherstellen das alle Klassen geladen werden. Hierzu implementieren wir den __autoload Interzeptor. Dieser lädt die Klasse bei Instanzierung automatisch. Voraussetzung in diesem Beispiel ist, dass alle Dateien auf der selbenen Ebene liegen. Nun instanzieren wir zuerst ein Linkobjekt mit der ID=1 und geben das Objekt dann mit einer einfachen print Anweisung aus. Danach machen wir dasselbe für einen zweiten Link mit der ID=2. Da die Link Klasse den __toString Interzeptor implementiert und dieser einfach den Link als String zurückliefert, ist diese Art von Aufruf möglich.

Das vorherige Beispiel funktioniert natürlich nur mit einer MySQL Datenbank und ebenfalls nur mit der MySQLi Erweiterung. Werden mehrere Datenbanken benötigt oder muss eine andere Erweiterung bzw. ein anderes Framework zum ansprechen der Datenbank verwendet werden, muss das Muster leicht abgewandelt werden. Ein Grund könnte zum Beispiel sein das die implementierte Applikation auf einem Kundenserver läuft, der das MySQLi Modul “ext/mysqli” aus irgendwelchen Gründe nicht mit einkompiliert hat. Dies aber nur am Rande wen die Abwandlung interessiert, sollte in naher Zukunft noch einmal hier vorbeischauen, wir werden hierzu in kürze einen eigenen Blog Beitrag veröffentlichen.

Fazit:
Das Singletonmuster stellt hier nicht nur sicher, dass immer die gleiche Datenbank Instanz übergeben wird, sondern bietet auch noch einen einfach API Aufruf an, um ein Datenbankobjekt in einer Klasse zu kapseln. Es gibt Situationen wie diese, in denen ein Singleton sehr hilfreich sein kann, aber auch hier gilt, wie für alle Entwurfsmuster, nur einsetzen wenn es sinnvoll ist. Ich hoffe das Beispiel ist gut nachvollziehbar. Über Anregungen, Verbesserungen und Feedback würde ich mich freuen.

Wenn dir dieser Beitrag gefällt, lade den Autor doch mal zu einem Drink ein ;-)

Broadcast us
  • Yigg
  • Webnews.de
  • Digg
  • MisterWong
  • del.icio.us
  • Technorati
  • DZone
  • Facebook
  • Google Bookmarks
  • Reddit
  • StumbleUpon
  • TwitThis

Tags: , , , , , ,

4 Responses

  1. der Artikel gefällt mir, es gibt aber keinen Drink. Dafür gibt es aber einen YiGG von mir. Der soll angeblich deutlich mehr wert sein, als ein Drink.

  2. Danke, wir freuen uns über jede Art von Rückmeldung, sei es ein Drink, ein YiGG, DIGG oder einfach nur ein Kommentar :-)

  3. Hallo,

    ich hatte für eine blog software (plosxom) ein ähnliches Problem. Plugins sollten auf Config, Smarty-Object etc zugreifen können, ohne mit “global” rumfummeln zu müssen. Ich hatte zuerst eine ähnliche Implementierung wie die hier beschriebene. Allerdings soll die Software portabel sein, also unter 4+5 laufen. Somit musste ich es in die Tonne treten und neu schreiben.

    Ich verwende jetzt eine Klasse Plugin, von der die Plugins erben (mittels “extends Plugin”), die einen Konstruktor hat, dem ich alles nötige als Referenz übergebe. Plugins selber haben keinen Konstruktor, sondern werden nach dem Laden per ->register() initialisiert. Wenn nun ein Plugin irgendwas ändert, zb. die Config, dann ist das allen anderen Plugins und auch der Core Engine verfügbar, weil es Referenzen sind. Es gibt zwar keine Absicherung dagegen, dass jemand so ein Objekt kopiert o.ä. aber es funktioniert zumindest und auch mit php4.

    Trotzdem guter Artikel, den hätt ich mal früher finden sollen :)

  4. [...] kurzem haben wir das Entwurfsmuster Singleton an einem PHP Beispiel vorgestellt. Das Singletonmuster gewährleistet, dass immer nur eine Instanz einer Klasse zur [...]

Leave a Reply