Erweitern von Singletons in PHP

Lesezeit: 6 Minuten

Benutzer-Avatar
Johan Fredrik Varen

Ich arbeite in einem Web-App-Framework, und ein Teil davon besteht aus einer Reihe von Diensten, die alle als Singletons implementiert sind. Sie alle erweitern eine Service-Klasse, in der das Singleton-Verhalten implementiert ist, und sehen in etwa so aus:

class Service {
    protected static $instance;

    public function Service() {
        if (isset(self::$instance)) {
            throw new Exception('Please use Service::getInstance.');
        }
    }

    public static function &getInstance() {
        if (empty(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}

Wenn ich nun eine Klasse namens FileService wie folgt implementiert habe:

class FileService extends Service {
    // Lots of neat stuff in here
}

… das Aufrufen von FileService::getInstance() ergibt keine FileService-Instanz, wie ich es möchte, sondern eine Service-Instanz. Ich nehme an, das Problem hier ist das Schlüsselwort “self”, das im Service-Konstruktor verwendet wird.

Gibt es eine andere Möglichkeit, das zu erreichen, was ich hier will? Der Singleton-Code besteht nur aus wenigen Zeilen, aber ich möchte trotzdem jede Coderedundanz vermeiden, wann immer ich kann.

Benutzer-Avatar
Amy B

Code:

abstract class Singleton
{
    protected function __construct()
    {
    }

    final public static function getInstance()
    {
        static $instances = array();

        $calledClass = get_called_class();

        if (!isset($instances[$calledClass]))
        {
            $instances[$calledClass] = new $calledClass();
        }

        return $instances[$calledClass];
    }

    final private function __clone()
    {
    }
}

class FileService extends Singleton
{
    // Lots of neat stuff in here
}

$fs = FileService::getInstance();

Wenn Sie PHP < 5.3 verwenden, fügen Sie dies auch hinzu:

// get_called_class() is only in PHP >= 5.3.
if (!function_exists('get_called_class'))
{
    function get_called_class()
    {
        $bt = debug_backtrace();
        $l = 0;
        do
        {
            $l++;
            $lines = file($bt[$l]['file']);
            $callerLine = $lines[$bt[$l]['line']-1];
            preg_match('/([a-zA-Z0-9\_]+)::'.$bt[$l]['function']."https://stackoverflow.com/", $callerLine, $matches);
        } while ($matches[1] === 'parent' && $matches[1]);

        return $matches[1];
    }
}

  • FYI: Dieser Code verwendet get_called_class, hinzugefügt in PHP 5.3. In früheren Versionen ist dies etwas schwieriger.

    – Karl

    27. Juni 2010 um 2:39 Uhr

  • Heilige Huch, das ist beängstigend. Stell dir vor, du rufst an getInstance ein Dutzend Mal, das sind ein Dutzend Öffnungen und ein Dutzend Lesevorgänge der Klassendatei.

    – Karl

    27. Juni 2010 um 3:01 Uhr

  • Aus diesem Grund sollten die Leute auf das neueste und beste upgraden ^^

    – Amy B

    27. Juni 2010 um 3:04 Uhr

  • Vielen Dank! Ich habe mir diese Funktion direkt angesehen, nachdem ich die Frage gestellt hatte, aber ich war mir nicht sicher, wie ich sie verwenden sollte, um das Problem zu lösen. Jetzt muss ich nur noch warten, bis die Web-Hotel-Jungs auf PHP5.3 upgraden 🙂

    – Johan Fredrik Varen

    27. Juni 2010 um 3:06 Uhr

  • @Johan Vielleicht möchten Sie die Singletons ganz fallen lassen. Sie führen eine harte Kopplung in Ihre Anwendung ein und sind schwer zu testen. Sie können das Problem, dass möglicherweise nur eine Instanz vorhanden ist, mit einem Dependency Injection Framework oder einer Registrierung lösen.

    – Gordon

    27. Juni 2010 um 10:46 Uhr

Benutzer-Avatar
Johan Fredrik Varen

Hätte ich in der Klasse 5.3 mehr aufgepasst, hätte ich gewusst, wie ich das selbst lösen kann. Unter Verwendung der neuen späten statischen Bindungsfunktion von PHP 5.3 glaube ich, dass der Vorschlag von Coronatus wie folgt vereinfacht werden kann:

class Singleton {
    protected static $instance;

    protected function __construct() { }

    final public static function getInstance() {
        if (!isset(static::$instance)) {
            static::$instance = new static();
        }

        return static::$instance;
    }

    final private function __clone() { }
}

Ich habe es ausprobiert, und es funktioniert wie ein Zauber. Pre 5.3 ist jedoch immer noch eine ganz andere Geschichte.

  • Es scheint, dass es nur ein einziges Feld gibt $instance für alle Unterklassen, also nur das Singleton der Klasse wo getInstance() zuerst aufgerufen wird, wird zurückgegeben.

    – C-Otto

    21. November 2013 um 19:12 Uhr

  • Das ist der naive Ansatz, der leider überhaupt nicht funktionieren kann, wie C-Otto bereits erwähnt hat. Abgestimmt: Mach das nicht zu Hause 😉

    – Phil

    10. März 2015 um 15:09 Uhr

  • Wie sie oben sagten … es ist falsch, dies gibt Ihnen die Instanz der ersten Klasse, die getInstance () verwendet hat

    – Juan

    2. Juni 2015 um 14:33 Uhr

Dies ist Johans Antwort behoben. PHP5.3+

abstract class Singleton
{
    protected function __construct() {}
    final protected function __clone() {}

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

        if (null === $instance)
        {
            $instance = new static();
        }

        return $instance;
    }
}

Ich habe eine gute Lösung gefunden.

Folgendes ist mein Code

abstract class Singleton
{
    protected static $instance; // must be protected static property ,since we must use static::$instance, private property will be error

    private function __construct(){} //must be private !!! [very important],otherwise we can create new father instance in it's Child class 

    final protected function __clone(){} #restrict clone

    public static function getInstance()
    {
        #must use static::$instance ,can not use self::$instance,self::$instance will always be Father's static property 
        if (! static::$instance instanceof static) {
            static::$instance = new static();
        }
        return static::$instance;
    }
}

class A extends Singleton
{
   protected static $instance; #must redefined property
}

class B extends A
{
    protected static $instance;
}

$a = A::getInstance();
$b = B::getInstance();
$c = B::getInstance();
$d = A::getInstance();
$e = A::getInstance();
echo "-------";

var_dump($a,$b,$c,$d,$e);

#object(A)#1 (0) { }
#object(B)#2 (0) { } 
#object(B)#2 (0) { } 
#object(A)#1 (0) { } 
#object(A)#1 (0) { }

Sie können verweisen http://php.net/manual/en/language.oop5.late-static-bindings.php
Für mehr Information

Ich bin auf diese Frage gestoßen, weil ich eine Singleton-Klasse zum Verwalten eines Cache-ähnlichen Objekts verwende und es erweitern wollte. Die Antwort von Amy B sah für meinen Geschmack etwas zu kompliziert aus, also habe ich ein bisschen weiter gegraben und das ist, was ich herausgefunden habe, funktioniert wie ein Zauber:

abstract class Singleton
{
    protected static $instance = null;

    protected function __construct()
    {
    }

    final public static function getInstance()
    {
        if (static::$instance === null) {
            static::$instance = new static();
        }
        return static::$instance;
    }

    final private function __clone()
    {
    }
}

class FileService extends Singleton
{
  protected static $instance = null;
}
    
$fs = FileService::getInstance();

Durch einfaches Überschreiben der Klasseneigenschaft $instance wird das Problem behoben. Nur mit PHP 8 getestet, aber ich vermute, dass dies auch für ältere Versionen funktioniert.

Die Verwendung eines Merkmals anstelle einer abstrakten Klasse ermöglicht die Erweiterung einer Singleton-Klasse.

Verwenden Sie die Eigenschaft SingletonBase für eine übergeordnete Singleton-Klasse.

Verwenden Sie das Merkmal SingletonChild für seine untergeordneten Singletons.

interface Singleton
{

    public static function getInstance(): Singleton;

}

trait SingletonBase
{

    private static $instance=null;

    abstract protected function __construct();

    public static function getInstance(): Singleton {

       if (is_null(self::$instance)) {

          self::$instance=new static();

       }

       return self::$instance;

    } 

    protected function clearInstance(): void {

        self::$instance=null;

    }

    public function __clone()/*: void*/ {

        trigger_error('Class singleton '.get_class($this).' cant be cloned.');
    }

    public function __wakeup(): void {

        trigger_error('Classe singleton '.get_class($this).' cant be serialized.');

    }

}

trait SingletonChild
{

    use SingletonBase;

}

class Bar
{

    protected function __construct(){

    }

}

class Foo extends Bar implements Singleton
{

      use SingletonBase;

}

class FooChild extends Foo implements Singleton
{

      use SingletonChild; // necessary! If not, the unique instance of FooChild will be the same as the unique instance of its parent Foo

}

Benutzer-Avatar
Stanislaw Stankow

    private static $_instances = [];

    /**
     * gets the instance via lazy initialization (created on first usage).
     */
    public static function getInstance():self
    {
        $calledClass = class_basename(static::class);

        if (isset(self::$_instances[$calledClass])) {
            self::$_instances[$calledClass] = new static();
        }

        return self::$_instances[$calledClass];
    }

Das einzige Problem mit diesem ist, wenn Sie Singletons mit dem gleichen Namen haben.

1114740cookie-checkErweitern von Singletons in PHP

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy