Вариантов тут на мой взгляд может быть несколько. Первый — пересмотреть архитектуру приложения. Возможно проще зарегистрировать все объекты, претендующие на Singleton, в некоторой коллекции, и обращаться к ним исключительно через неё. Это позволит избежать случайного создания двух копий объекта. Однако, с уверенностью утверждать, что в приложении в определённый момент выполнения существует только одна копия объекта, при таком раскладе нельзя, так как никто не может помешать создать объект напрямую, в обход коллекции.
Второй способ заключается в реализации суперкласса Singleton'а, от которого будут наследоваться все классы-Singleton'ы. Однако в его реализации на PHP есть один подводный камень — позднее связывание (late/dynamic binding) в статических методах. Точнее — определение имени текущего класса в статическом методе getInstance.
Стандартные средства вроде метода get_class тут применить нельзя, так как никакого объекта у нас ещё собственно нет. В голову приходит отражение, но быстро понимаешь, что тут оно ни причём, так как исходной точкой для него является имя объекта, которое мы собственно и пытаемся узнать.
И тут нам на помощь приходит механизм по названием «позднее статическое связывание» (late static binding), доступный начиная с версии 5.3.0. Суть его заключается в возможности сослаться на вызываемый класс в контексте статического наследования. Другими словами, начиная с версии 5.3.0 мы можем использовать функцию get_called_class для получения имени вызываемого класса в рамках статического метода.
Зная вышеописанное, нетрудно создать суперкласс для реализации шаблона Singleton.
- /**
- * Singleton pattern implementation
- */
- abstract class Singleton {
-
- /**
- * Collection of instances
- * @var array
- */
- private static $_aInstance = array();
-
- /**
- * Private constructor
- */
- private function __construct(){}
-
- /**
- * Get instance of class
- */
- public static function getInstance() {
-
- // Get name of current class
- $sClassName = get_called_class();
-
- // Create new instance if necessary
- if( !isset( self::$_aInstance[ $sClassName ] ) )
- self::$_aInstance[ $sClassName ] = new $sClassName();
- $oInstance = self::$_aInstance[ $sClassName ];
-
- return $oInstance;
- }
-
- /**
- * Private final clone method
- */
- final private function __clone(){}
- }
* This source code was highlighted with Source Code Highlighter.Дополним код небольшим тестом
- class Example extends Singleton {}
- $oExample1 = Example::getInstance();
- $oExample2 = Example::getInstance();
- echo ( is_a( $oExample1, 'Example' ) && $oExample1 === $oExample2)
- ? 'Same' : 'Different', "\n";
* This source code was highlighted with Source Code Highlighter.результатом которого будет, как вы уже догадались, «Same».
Стоит заметить, что существуют механизмы получения имени вызываемого класса в контексте статической функции в PHP версии до 5.3.0 с помощью анализа стека вызова методом debug_backtrace. Однако, на мой взгляд, данный подход ректален чуть более, чем полностью.
P.S.
Согласен, что наследование — не лучший механизм в данном случае. На мой взгляд, если бы PHP поддерживал аннотации и АОП, было бы намного удобней помечать классы аннотацией Singleton и реализовать аспект, осуществляющий инъекцию необходимого для реализации Singleton'a кода в помеченные классы. Однако, на сколько мне известно, ни аннотаций (как конструкции языка), ни АОП в PHP в ближайшее будущее не планируется.

