PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


PHP 5.3 Feature: Late static binding (LSB)

with 9 comments

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
{
}

Written by Michael Kliewe

April 17th, 2011 at 2:44 pm

Posted in PHP

Tagged with , , , ,

9 Responses to 'PHP 5.3 Feature: Late static binding (LSB)'

Subscribe to comments with RSS or TrackBack to 'PHP 5.3 Feature: Late static binding (LSB)'.

  1. Hey,

    die abstrakte Singleton-Klasse kann man meines Erachtens auch einfacher gestalten:
    abstract class Singleton {
    protected function __construct() {}
    final private function __clone() {}

    final static public function getInstance() {
    static $instance = null;

    return $instance ?: $instance = new static;
    }
    }
    Siehe: http://blog.gi-project.de/2010/08/singleton-dank-late-static-binding-vererbbar/

    Ich bin mir allerdings nicht mehr sicher, warum ich den Konstruktur protected gesetzt habe. Sollte vermutlich auch ein „final private“ sein.

    Julian

    18 Apr 11 at 08:24

  2. @ Julian :

    Du hast recht. Allerdings wurde in dem oberen Beispiel in dem Singleton-Abstract auch noch Methoden der eigentlichen „Auto“-Klasse mit eingefügt. Wenn man es schöner machen möchte, dann müsste man eine zusätzliche Klasse implementieren. Dadurch wird die Singleton-Klasse dann automatisch fast so kompakt wie die von dir. Beispiel:

    BMW extends Car
    Car (enthält Methoden wie z.B. getDriverName) extends Singleton

    Bezüglich der getInstance Methode: Ich persönlich finde die ausführlichere Variante von hier etwas schöner, da diese verständlicher für Programmier-Anfänger ist.

    Marvin Thomas Rabe

    18 Apr 11 at 16:26

  3. @Julian: Der Constructor kann/sollte protected sein, damit er von einer erbende Klasse verwendet werden kann, jedoch von außen nicht direkt aufrufbar ist.

    Ich mag die kurze Singleton-Schreibweise auch lieber 😉

    Daniel

    18 Apr 11 at 17:27

  4. @Marvin: Sind die konkreten „Auto“-Klasse Methoden nicht auch in der kürzeren Version integrierbar? Hatte mich nur auf das getInstance() bezogen .. alles andere sollte ja genauso laufen denke ich

    @Daniel: Ah das könnte der Gedanke dahinter gewesen sein, ja .. damit man beim ersten Konstruieren der Singleton-Klasse auch schon etwas ausführen lassen kann.

    Julian

    19 Apr 11 at 16:51

  5. Schöner Artikel Michael!

    Aber wieso ist die Klasse Singleton abstrakt?
    Sie besitzt doch keine abstrakten Methoden?!

    Florian

    20 Apr 11 at 10:10

  6. […] binding vererben, einen sehr hübschen Blogeintrag vom 'phpgangsta' mit Erklärungen findest du hier. Viele Grüße Basti __________________ ++++ Boxer stolz: Sohn schlägt sich gut in der Schule […]

  7. […] PHP Gansgta beschreibt das Feature “Late-State-Binding” […]

  8. Wenn im letzten Beispiel

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

    die Objekte der Kindklassen in der $_instance der Elternklasse gespeichert werden braucht man doch nicht mehr static:: oder?

    Eigentlich reicht es dann doch auch so:

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

    Denke ich, oder gibt es da dann nen Unterschied?

    Dennis

    20 Dez 11 at 19:25

  9. Ich schließe mich Dennis an. Meiner Meinung nach liegt der Schlüssel hier im Aufruf von get_called_class(). Der Rest benötigt kein „static“ keyword.

    Mike

    6 Jun 12 at 09:04

Leave a Reply

You can add images to your comment by clicking here.