Как стать автором
Обновить

Исправляем паттерн проектирования — Singleton в PHP

Время на прочтение2 мин
Количество просмотров6.7K

Недавно я писал о том, как сломать паттерн проектирования — Singleton в PHP. После написания статьи я искал новый вариант реализации паттерна: есть ли способ создать Singleton в PHP, не давая возможности создавать новые экзепляры класса с помощью Closure::bind()?


How to fix Singleton in PHP


Я придумал много различных вариантов, но так же находил способы их обойти. Мне уже казалось, что не получится создать новую реализацию, но пришла идея и я начал её проверять.


Вот, собственно, код и ссылка на песочницу. Давайте его разберём:


<?php

final class Singleton
{
    public static function getInstance()
    {
        static $instance;

        if (null === $instance) {
            $instance = new self();
        }

        return $instance;
    }

    private function __construct()
    {
        static $hasInstance = false;

        if ($hasInstance) {
            \trigger_error('Class is already instantiated', \E_USER_ERROR);
        }

        $hasInstance = true;
    }

    private function __clone()
    {
        \trigger_error('Class could not be cloned', \E_USER_ERROR);
    }

    private function __wakeup()
    {
        \trigger_error('Class could not be deserialized', \E_USER_ERROR);
    }
}

$s1 = Singleton::getInstance();
\var_dump(\spl_object_id($s1));

$createNewInstance = function () {
    return new self();
};
$newInstanceClosure = Closure::bind($createNewInstance, $s1, Singleton::class);

// Fatal error:  Class is already instantiated
$newInstanceClosure();

статическую переменную $instance мы переносим в метод getInstance(), чтобы не иметь возможности получить к ней доступ с помощью операторов self и static в анонимной функции.


В конструкторе класса так же добавляем статическую переменную, которая хранит булево-значение. При создании нового объекта мы проверяем значение этой переменной: если там хранится false — мы устанавливаем этой переменной значение true и объект успешно создаётся. При попытке создания нового объекта, код попадёт в if, так как при создании первого объекта мы записали значение true в статическую переменную $hasInstance, затем в теле if'а мы вызовем пользовательскую ошибку с текстом Class is already instantiated.


В магических методах __clone() и __wakeup() мы так же вызываем пользовательские ошибки с соответствующими сообщениями для того, чтобы не иметь возможности создать объекты с помощью оператора clone и механизма сериализации в анонимной функции.


При желании можно бросать исключения вместо пользовательских ошибок.


Таким образом возможно создать всего один объект Singleton класса. Пока что я не нашёл способа сломать данную реализацию паттерна, поэтому если у кого-то получится это сделать — напишите об этом в комментарии :)


Спасибо за внимание!

Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
Всего голосов 14: ↑10 и ↓4+6
Комментарии6

Публикации

Истории

Работа

PHP программист
102 вакансии

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань