Казалось бы, из всех шаблонов проектирования что может быть проще всем известного синглетона. Во многих классических примерах реализации на разных языках программирования она может занимать всего пару десятков строк а того и меньше.

image Так получилось, что я реализую этот шаблон вот уже второй год начиная с первого выхода PHP 5.3 в 2009 году. В то время у его предшественника версии 5.2 не было позднего статического связывания и для создания экземпляра класса в метод приходилось передавать его имя, что казалось мне архинеудобным.

С выходом PHP 5.4, взглянув еще раз на старую реализацию и на новые возможности языка, я переписал этот шаблон еще раз получив — как мне казалось тогда и кажется сейчас — конечный вариант.

Подробности реализации ниже.

Сразу бы хотелось отметить основные особенности:

  • Параметрическое порождение. Позволяет создавать экземпляры классов используя сигнатуру вызова метода ::getInstance. Каждой сигнатуре будет соответствовать свой экземпляр класса. По умолчанию такой тип порождения отключен. Включается в дочерних классах переопределением метода ::useParametricInstantiation.
  • Получение дочернего объекта по имени родительского класса. Позволяет ссылаться на дочерние классы из родительских а также из других классов не зная их имени.
  • Создание дочернего класса по имени родительского класса. Аналогично второму пункту, только в случае если дочерний объект не был
    * Trait TSingleton.
    * An implementation of the Singleton design pattern.
    */
    namespace Traits;
    /**
    * var array variable holding all created objects.
    */
    $objectPool = [];

    trait TSingleton
    {

    /**
    * Do not allow creating object by the new operator.
    *
    * final
    * access private
    * return void
    */
    function __construct() { }

    /**
    * Do not allow cloning object.
    *
    * final
    * access private
    * return void
    */
    private function __clone() { }

    /**
    * Called when class is being instantiated.
    *
    * access protected
    * return void
    */
    protected function onCreate() { }

    /**
    * User-level initialization routine.
    *
    * return void
    */
    protected function init() { }

    /**
    * Returns true if child class has a parent specified by the mask.
    *
    * param string $child
    * param string $parentMask
    * final
    * static
    * access public
    * return boolean
    */
    static function hasParentClass($child, $parentMask)
    {
    $currentClass = get_parent_class($child);
    if (!$currentClass)
    return false;
    do
    {
    if (strpos($currentClass, $parentMask) !== false)
    return true;
    }
    while ($currentClass = get_parent_class($currentClass));

    return false;
    }

    /**
    * Returns instance of child class using its parent' class name specified
    * by the mask. Always returns an array.
    *
    * param string $parentMask Any substring of parent's fully qualified class name.
    * final
    * static
    * access public
    * return array|null
    */
    static function getObjectByParent($parentMask)
    {
    global $objectPool;

    foreach ($objectPool as $class => $container)
    if (self::hasParentClass($class, $parentMask))
    return array_values($container);

    return null;
    }

    /**
    * Finds object(s) by the mask of its(their) parent's class namе. If not
    * found the method will create it. Always returns an array.
    *
    * param string $parentMask
    * param array $initArgs
    * final
    * static
    * access public
    * return array|null
    */
    static function getObjectByParentSafe($parentMask, $initArgs = [])
    {
    $child = self::getObjectByParent($parentMask);
    if ($child !== null)
    return $child;

    // Look up all declared classes.
    $result = [];
    foreach (get_declared_classes() as $class)
    {
    if (self::hasParentClass($class, $parentMask))
    {
    $result[] = call_user_func_array(($class. '::getInstance'), $initArgs);
    }
    }

    return count($result)? $result: null;
    }

    /**
    * Returns child object(s) of the parent class that called the method.
    *
    * see TSingleton::getObjectByParent
    * final
    * static
    * access public
    * return array|null
    */
    static function getMyChild()
    {
    return self::getObjectByParent(get_called_class());
    }

    /**
    * Safe variant of ::getMyChild.
    *
    * see TSingleton::getObjectByParentSafe
    * final
    * static
    * access public
    * return array
    */
    static function getMyChildSafe()
    {
    $initArgs = func_get_args();
    return self::getObjectByParent(get_called_class(), $initArgs);
    }

    /**
    * Returns class instance.
    *
    * static
    * final
    * access public
    * return TSingleton
    */
    static function getInstance()
    {
    global $objectPool;

    $argsArray = func_get_args();
    $class = get_called_class();

    if (static::useParametricInstantiation() && count($argsArray))
    {
    $fingerprint = '';
    foreach ($argsArray as $arg)
    $fingerprint .= serialize($arg);
    $key = md5($class. $fingerprint);
    }
    else // Use class name as a key.
    $key = $class;

    if (!isset($objectPool[$class]))
    $objectPool[$class] = []; // Init class objects container.

    if (isset($objectPool[$class][$key]))
    return $objectPool[$class][$key];

    $instance = new $class();

    // Add instance to the objects pool.
    $objectPool[$class][$key] = $instance;

    call_user_func_array([$instance, 'onCreate'], $argsArray);
    $instance->init();

    return $instance;
    }

    /**
    * Enables or disables the parametric class instantiation. Disabled by default.
    *
    * access public
    * static
    * return boolean
    */
    static function useParametricInstantiation()
    {
    return false;
    }
    }
  • :
    Все созданные объекты теперь хранятся в одной переменной. Область видимости этой переменной (по просьбе трудящихся масс) ограничена примесью.

    Дабы не рвать шаблоны нестандартным шаблоном синглетон с параметрическим порождением отныне называется мультитоном (Multiton).