2010-06-27 17 views
21

Estoy trabajando en un marco de aplicación web, y parte de él consiste en una serie de servicios, todos implementados como singletons. Todas ellas dan una clase de servicio, donde se implementa el comportamiento único, buscando algo como esto:Extendiendo singletons en 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; 
    } 
} 

Ahora, si tengo una clase llamada FileService implementado como esto:

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

... llama FileService :: getInstance() no generará una instancia de FileService, como yo quiero, sino una instancia de servicio. Supongo que el problema aquí es la palabra clave "self" utilizada en el constructor del Servicio.

¿Hay alguna otra manera de lograr lo que quiero aquí? El código singleton es solo unas pocas líneas, pero me gustaría evitar cualquier redundancia de código siempre que pueda.

Respuesta

46

Código:

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

Si utiliza PHP < 5.3, añadir esto también:

// 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: Este código utiliza [ 'get_called_class'] (http://us2.php.net/manual/en/function.get-called-class.php), añadido en PHP 5.3. Hacer esto en versiones anteriores es un poco más complicado. – Charles

+0

Gracias Charles, actualizó la respuesta para solucionarlo. –

+1

Santos sagrados, eso da miedo. Imagine que llama a 'getInstance' una docena de veces, eso es una docena de aperturas y una docena de lecturas del archivo de clase. – Charles

7

Tuvimos que pagar más atención en 5.3 clase, habría sabido cómo resolver esto por mí mismo. El uso de la nueva característica de unión estática finales de PHP 5.3, creo proposición Coronatus' se puede simplificar en esto:

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

lo probé, y funciona como un encanto. Pre 5.3 aún es una historia completamente diferente, sin embargo.

+4

Parece que solo hay un solo campo '$ instance' para todas las subclases, por lo que solo se devuelve el singleton de la clase donde' getInstance() 'se llama primero. –

+0

Este es el enfoque ingenuo que desafortunadamente no puede funcionar en absoluto como ya mencionó C-Otto. Downvoted: No hagas esto en casa ;-) – Phil

+0

Como dijeron arriba ... está mal, esto te dará la instancia de la primera clase que usó getInstance() – Juan

0

Se ha solucionado la respuesta de 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; 
    } 
}