Расширение синглотонов в PHP
Я работаю в рамках веб-приложения, и часть его состоит из нескольких сервисов, которые реализованы как одиночные. Все они расширяют класс Service, в котором реализовано поведение singleton, выглядя примерно так:
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;
}
}
Теперь, если у меня есть класс, называемый FileService, реализованный следующим образом:
class FileService extends Service {
// Lots of neat stuff in here
}
... вызов FileService:: getInstance() не даст экземпляр FileService, как я хочу, но экземпляр службы. Я предполагаю, что проблема здесь - это ключевое слово self, используемое в конструкторе Service.
Есть ли другой способ добиться того, чего я хочу здесь? Одноэлементный код - всего несколько строк, но я все равно хотел бы избежать избыточности кода всякий раз, когда могу.
Ответы
Ответ 1
Код:
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();
Если вы используете PHP < 5.3, добавьте это тоже:
// 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];
}
}
Ответ 2
Если бы я уделял больше внимания классу 5.3, я бы знал, как решить это сам. Используя новую функцию поздней статической привязки PHP 5.3, я считаю, что предложение Coronatus можно упростить:
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() { }
}
Я попробовал, и это работает как шарм. Pre 5.3 все еще совсем другая история.
Ответ 3
Это фиксированный ответ Йохана. 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;
}
}