2010-06-27 15 views
21

Sto lavorando in un framework di app Web e parte di esso consiste in un numero di servizi, tutti implementati come singleton. Tutti estendono una classe di servizio, in cui viene attuato il comportamento Singleton, cercando qualcosa di simile:Estensione di singleton in PHP

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

Ora, se ho una classe chiamata FileService implementata in questo modo:

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

... chiamando FileService :: getInstance() non genererà un'istanza di FileService, come lo voglio io, ma un'istanza di Servizio. Presumo che il problema qui sia la parola chiave "self" utilizzata nel costruttore del servizio.

C'è qualche altro modo per ottenere ciò che voglio qui? Il codice singleton è solo poche righe, ma vorrei comunque evitare ogni ridondanza di codice ogni volta che posso.

risposta

46

Codice:

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(); 

Se si utilizza PHP < 5.3, aggiungere anche questo:

// 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'].'/', $callerLine, $matches); 
     } while ($matches[1] === 'parent' && $matches[1]); 

     return $matches[1]; 
    } 
} 
+0

FYI: Questo codice utilizza [ 'get_called_class'] (http://us2.php.net/manual/en/function.get-called-class.php), aggiunto in PHP 5.3. Fare questo nelle versioni precedenti è un po 'più complicato. – Charles

+0

Grazie Charles, ho aggiornato la risposta per risolverlo. –

+1

Holy Yikes, è spaventoso. Immagina di chiamare 'getInstance' una dozzina di volte, cioè una dozzina di aperture e una dozzina di letture del file di classe. – Charles

7

se avessi pagato più attenzione a 5.3 di classe, avrei saputo come risolvere questo me stesso. Utilizzando la nuova funzione di legame statico in ritardo di PHP 5.3, credo proposizione Coronatus' può essere semplificata in questo:

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

ho provato, e funziona come un fascino. Pre 5.3 è ancora una storia completamente diversa, però.

+4

Sembra che ci sia un solo campo '$ istanza' per tutte le sottoclassi, quindi viene restituito solo il singleton della classe in cui' getInstance() 'viene chiamato per primo. –

+0

Questo è l'approccio ingenuo che sfortunatamente non può funzionare come C-Otto ha già menzionato. Downvoted: Non farlo a casa ;-) – Phil

+0

Come hanno detto sopra .. è sbagliato, questo ti darà l'istanza della prima classe che ha usato getInstance() – Juan

0

Questa è la risposta di Johan. PHP 5.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; 
    } 
}