Паттерн Singleton в PHP
30-03-2019Время чтения ~ 4 мин.PHP/ООП 11118
Можно сказать, что Singleton в PHP наиболее часто используемый паттерн. Смысл его работы в том, чтобы гарантировать существование одного единственного экземпляра переменной (класс, массив, не важно) во всём приложении. Зачем это вообще нужно?
Предположим сайт использует работу с базой данных MySQL. Для этого необходимо выполнить процедуру подключения, которая возвращает дескриптор соединения. Дальше с этой базой можно выполнять различные операции. Если у нас php-проект из множества файлов, то нужно сделать так, чтобы дескриптор соединения был доступен в любой части проекта. Если этого не сделать, то при каждом обращении к базе придется заново создавать новое соединение, что не очень хорошо скажется на нагрузке сервера.
Другой пример — единая конфигурация приложения. Скажем те же параметры доступа к базе можно хранить в каком-то одном файле, который один раз считывается и помещается в массив и доступен из любой части программы.
Для решения именно таких задач и используется Singleton.
Считается, что Singleton предназначен только для ООП (объектно ориентированное программирование), но на самом деле это не так. Паттерн легко реализуется и на обычных функциях.
Глобальная область видимости
Самым простым вариантом решения, будет использование глобальной области видимости.
$SI = array(); function f1() { global $SI; ... }
В некоторых случаях глобальная область видимости, действительно может использоваться, поскольку имеет очень простую и быструю реализацию. Но использование глобальной области видимости считается не самым лучшим вариантом (по разным причинам), поэтому нужно придумать какой-то другой вариант.
Статичные переменные
В PHP существуют статичные переменные, которые имеют область видимости только внутри своей функции, но при этом сохраняют своё значение после выхода из функции. Именно на статичных переменных основываются реализации Singleton.
Рассмотрим начальную задачу — получение только одного экземпляра переменной. Функция простая:
function get_instance() { static $SI = NULL; if (is_null($SI)) $SI = array(); return $SI; }
Переменная $SI объявлена статической и инициализируется значением NULL. При первом вызове проверяется условие, и если $SI равна NULL (то есть ни разу не вызывалась), она инициализируется. В нашем примере просто как массив.
При повторных вызовах $SI уже существует и функция отдает её как есть.
$e1 = get_instance(); print_r($e1);
Если $SI будет содержать какие-то неизменные данные, например id подключения к базе данных или файл конфигурации, то данной реализации будет достаточно. Но, часто стоит задача изменить $SI - внести какие-то данные, чтобы потом они стали доступны при последующих вызовах.
Поэтому для решения этой задачи используется обращение по ссылке. В таком варианте мы будем получать не новую копию переменной, а ссылку на существующую.
function &get_instance() { static $SI = NULL; if (is_null($SI)) $SI = array(); return $SI; }
Изменения всего лишь в одном символе &. Теперь можно не только получить значение $SI, но и изменять его.
$e1 = &get_instance(); $e1[] = 'first'; $e2 = &get_instance(); $e2[] = 'second'; $all = get_instance(); print_r($all); Array ( [0] => first [1] => second )
Если менять $SI не требуется, то get_instance()
можно вызывать и без &, как это сделано для $all
.
Реализация на классах
Singleton через PHP-классы реализуется точно по этому же принципу — на статичных переменных и вызовах. Приведу простой практический пример:
class Singleton { private static $instance; private $var = array(); public static function get_instance() { if (empty(self::$instance)) self::$instance = new self(); return self::$instance; } public function getVar() { return $this->var; } public function setVar($v) { $this->var[] = $v; } }
Переменная $instance
также объявлена статичной, которая в методе get_instance
проверяется с помощью empty()
. Если она пустая, то инициализируется собственным классом.
Методы getVar
и setVar
и переменная $var
приведены как простой пример работы с этим классом:
$SI_1 = Singleton::get_instance(); $SI_1->setVar('first'); $SI_2 = Singleton::get_instance(); $SI_2->setVar('second'); $SI_3 = Singleton::get_instance(); print_r($SI_3->getVar()); Array ( [0] => first [1] => second )
Здесь создаются разные переменные, но все они по сути работают с одним объектом Singleton.
В «классической» реализации Singleton нужно ещё добавить запрет на вызов конструктора класса, его клонирование и т.п.
Функция-обёртка
В заключении хочу отметить, что часто, вместо статичного вызова метода класса, используют более простую функцию. Это позволяет немного упростить используемый код, как это например сделано в CodeIgniter.
function &get_instance() { return Singleton::get_instance(); }
Использование примеров выше:
$SI_1 = &get_instance(); $SI_1->setVar('first'); $SI_2 = &get_instance(); $SI_2->setVar('second'); $SI_3 = &get_instance(); print_r($SI_3->getVar()); Array ( [0] => first [1] => second )
Всегда хотел спросить, зачем в приведенном примере get_instance() делать static ?
private static $instance не достаточно, чтобы получать один и тот же экземпляр?
Спасибо.
Без static невозможно будет вызвать метод, не создавая экземпляр объект.