Недавно я писал о том, как сломать паттерн проектирования — Singleton в PHP. После написания статьи я искал новый вариант реализации паттерна: есть ли способ создать Singleton в PHP, не давая возможности создавать новые экзепляры класса с помощью Closure::bind()
?
Я придумал много различных вариантов, но так же находил способы их обойти. Мне уже казалось, что не получится создать новую реализацию, но пришла идея и я начал её проверять.
Вот, собственно, код и ссылка на песочницу. Давайте его разберём:
<?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 класса. Пока что я не нашёл способа сломать данную реализацию паттерна, поэтому если у кого-то получится это сделать — напишите об этом в комментарии :)
Спасибо за внимание!