Facebook
Twitter
Google+
Kommentare
0

PHP 5.3 Feature: Late static binding (LSB)

PHP 5.3 brachte unter anderem auch Late-Static-Binding (LSB). Ich würde tippen, dass einige von euch wissen was das ist, aber nur sehr wenige von euch dieses Feature bisher benötigt und eingesetzt haben. Ich möchte hier zwei kleine Beispiele zeigen, an dem klarer wird, wofür LSB benötigt wird:

class ClassA {
    public static function getName() {
        return self::name();
    }

    public static function getNameLSB() {
        return static::name();
    }

    public static function name() {
        return 'ClassA';
    }
}

class ClassB extends ClassA {
    public static function name() {
        return 'ClassB';
    }
}

echo ClassB::getName();       // ClassA
echo ClassB::getNameLSB();    // ClassB

Je nachdem ob self:: oder static:: genutzt wird, wird einmal die Elternmethode und einmal die Kindmethode aufgerufen. self:: bindet sich früh (beim Kompilieren) an seine Klasse, static:: erst bei der Ausführung (dann an die Kindklasse).

Nehmen wir an wir nutzen in einigen unserer Klassen das Singleton-Pattern. Wir wollen also dass von einer Klasse maximal ein Objekt erzeugt werden kann, wenn in der Vergangenheit bereits ein Objet erstellt wurde, soll dieses zurückgegeben werden. Der Code für eine solche Klasse sieht dann so aus:

class Bmw
{
    protected static $instance = null;

    // Konstruktor private, damit die Klasse nur aus sich selbst heraus instanziiert werden kann.
    private function __construct() {}

    // Diese statische Methode gibt die Instanz zurück.
    public static function getInstance()
    {
        if (self::$instance === NULL) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    // Klonen per 'clone()' von außen verbieten.
    private function __clone() {}
}

So weit so klar, wenn nun ein BMW Objekt benötigt wird muss, da der Konstruktor nicht genutzt werden kann, getInstance() genutzt werden, und die prüft, ob bereits ein Objekt existiert und liefert das zurück falls möglich.

Nehmen wir nun an, wir wollen das selbe auch mit einer Audi-Klasse tun. Bevor wir nun also Klassen anlegen und Code kopieren (die 3 oben gezeigten Methoden wären exakt gleich), können wir diese Singleton-Fähigkeit auch in eine Elternklasse auslagern. Ich füge noch ein Attribut für den Fahrernamen hinzu, dann kann man später das Problem besser sehen:

class Singleton
{
    protected static $_instance = null;
    protected $_driverName = null;

    // Konstruktor private, damit die Klasse nur aus sich selbst heraus instanziiert werden kann.
    private function __construct() {}

    /**
     * Diese statische Methode gibt die Instanz zurueck.
     *
     * @static
     * @return Singleton
     */
    public static function getInstance() {

        if (self::$_instance === NULL) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }

    // Klonen per 'clone()' von außen verbieten.
    private function __clone() {}

    public function setDriverName($driverName)
    {
        $this->_driverName = $driverName;
    }

    public function getDriverName()
    {
        return $this->_driverName;
    }
}

Die Kind-Klassen sehen dann so aus:

class Bmw extends Singleton
{
}

class Audi extends Singleton
{
}

Alles wunderbar denkt man. Doch leider kommt uns da ein kleines Problem in die Quere, und zwar nutzen beide Klassen (BMW, Audi) die selbe $_instance Klassenvariable, sodass die sich auch gegenseitig überschreiben. Das Grundproblem ist self::, denn das wird bereits beim Kompilieren aufgelöst, und nicht erst bei der Ausführung. Hier sieht man das Problem schön:

$bmw = Bmw::getInstance();
$bmw->setDriverName('Fahrer1');
var_dump($bmw);    // Singleton Objekt mit Fahrer1
$audi = Audi::getInstance();
var_dump($audi);    // Singleton Objekt mit Fahrer1

Obwohl wir beim $audi-Objekt keinen Fahrernamen setzen, erhalten wir “Fahrer1″ als Ergebnis. $bmw und $audi sind nun das selbe Objekt, genau das wollen wir nicht!

Die Lösung ist das neue LSB, wir ersetzen einfach self:: durch static::, und schon funktioniert es. static:: wird nicht beim Kompilieren aufgelöst, sondern erst später bei der Ausführung in den Kindklassen, und dann nutzt $bmw ein eigenes $_instance Attribut, $audi auch.

Die neuen Klassen:

abstract class Singleton
{
    protected static $_instance = null;
    protected $_driverName = null;

    // Konstruktor private, damit die Klasse nur aus sich selbst heraus instanziiert werden kann.
    private function __construct() {}

    /**
     * Diese statische Methode gibt die Instanz zurueck.
     *
     * @static
     * @return Singleton
     */
    public static function getInstance()
    {
        if (static::$_instance === NULL) {
            static::$_instance = new static();
        }
        return static::$_instance;
    }

    // Klonen per 'clone()' von außen verbieten.
    private function __clone() {}

    public function setDriverName($driverName)
    {
        $this->_driverName = $driverName;
    }

    public function getDriverName()
    {
        return $this->_driverName;
    }
}

class Bmw extends Singleton
{
    protected static $_instance = null;
}

class Audi extends Singleton
{
    protected static $_instance = null;
}

Und die Benutzung:

$bmw = Bmw::getInstance();
$bmw->setDriverName('Fahrer1');
var_dump($bmw);    // Bmw Objekt mit Fahrer1
$audi = Audi::getInstance();
var_dump($audi);   // Audi Objekt mit null

Möchte man dieses blöde statische Attribut in den Kindklassen vermeiden geht es auch so mittels get_called_class():

abstract class Singleton
{
    protected static $_instance = null;
    protected $_driverName = null;

    // Konstruktor private, damit die Klasse nur aus sich selbst heraus instanziiert werden kann.
    private function __construct() {}

    /**
     * Diese statische Methode gibt die Instanz zurueck.
     *
     * @static
     * @return Singleton
     */
    public static function getInstance()
    {
        $className = get_called_class();

        if (!isset(static::$_instance[$className])) {
            static::$_instance[$className] = new static();
        }
        return static::$_instance[$className];
    }

    // Klonen per 'clone()' von außen verbieten.
    private function __clone() {}

    public function setDriverName($driverName)
    {
        $this->_driverName = $driverName;
    }

    public function getDriverName()
    {
        return $this->_driverName;
    }
}

class Bmw extends Singleton
{
}

class Audi extends Singleton
{
}

flattr this!

Über den Autor

PHP Gangsta

Der zweitgrößte deutsche, eher praxisorientierte PHP-Blog von Michael Kliewe veröffentlicht seit Mitte 2009 Artikel für Fortgeschrittene.

Link erfolgreich vorgeschlagen.

Vielen Dank, dass du einen Link vorgeschlagen hast. Wir werden ihn sobald wie möglich prüfen. Schließen