Pull to refresh

Реализация шаблона проектирования Singleton на PHP 5.4

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

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).
Tags:
Hubs:
-12
Comments57

Articles

Change theme settings