- PHP Singleton (Одиночка)
- Суть паттерна
- PHP 5.0
- PHP 5.3
- PHP 5.4
- PHP 8
- Singleton (Одиночка)
- Определение
- Пример для PHP5
- Реализация шаблона Singleton
- The singleton pattern::the good, the bad, and the ugly
- The anatomy of a singleton pattern
- Why is it a singleton?
- Practical example::database class
- Class that doesn’t use a singleton to contact the database
- The singleton pattern::the good, the bad, and the ugly
PHP Singleton (Одиночка)
Singleton Pattern (single — единственный [англ.] ton — мода, стиль [англ.]) — один из наиболее известных шаблонов проектирования.
Суть паттерна
Одиночка — это порождающий паттерн проектирования, который гарантирует, что у класса есть только один экземпляр, и предоставляет к нему глобальную точку доступа.
Почти в любой объктно-ориентированной программе обычно существует один-два объекта, которые инициализируются в начале, и используются на всем протяжении работы приложения (доступ к базе данных, система сбора логов, разнообразные конфигураторы, блокировка файла в приложении).
Нет нужды каждый раз создавать объект класса, достаточно создать один экземпляр класса, в начале работы программы, и пользоваться им.
PHP 5.0
В РНР5 singleton определяется так:
class Singleton < private static $instance; /** * Защищаем от создания через new Singleton * @return Singleton */ private function __construct() <>/** * Защищаем от создания через клонирование * @return Singleton */ private function __clone() <> /** * Защищаем от создания через unserialize * @return Singleton */ private function __wakeup() <> /** * Возвращает единственный экземпляр класса * @return Singleton */ public static function getInstance() < if (self::$instance === null) < self::$instance = new self; >return self::$instance; > /** * Делаем что-нибудь */ public function doAction() <> > // Пример использования Singleton::getInstance()->doAction();
Преимущества данного метода очевидны:
- Мы используем ссылки на один и тот же экземпляр класса в разных частях проекта. Таким образом не нужно создавать новый объект каждый раз когда мы хотим воспользоваться каким-то методом — просто пользуемся ссылкой.
- Теперь не нужно передавать объект как аргумент, чтоб все части программы были «в курсе» о текущем состоянии объекта. Все ссылки указывают на один объект.
PHP 5.3
Начиная с PHP 5.3.0 была добавлена возможность Late Static Bindings, благодаря которой этот паттерн можно реализовать в виде абстрактного класса:
abstract class Singleton < /** * @return Singleton */ final public static function getInstance() < static $instance = null; if (null === $instance) < $instance = new static(); >return $instance; > final protected function __clone() <> protected function __construct() <> > class Foo extends Singleton < >class Bar extends Singleton < >var_dump(Foo::getInstance()); // object(Foo)[1] var_dump(Bar::getInstance()); // object(Bar)[2]
PHP 5.4
Начиная с PHP 5.4 был добавлен новый механизм обеспечения повторного использования кода при помощи трейтов:
private function __clone() < /* . @return Singleton */ >private function __wakeup() < /* . @return Singleton */ >public static function getInstance() < return self::$instance===null ? self::$instance = new static() : self::$instance; >> /** * Class Foo * @method static Foo getInstance() */ class Foo < use Singleton; private $bar = 0; public function incBar() < $this->bar++; > public function getBar() < return $this->bar; > > /* Применение */ $foo = Foo::getInstance(); $foo->incBar(); var_dump($foo->getBar()); $foo = Foo::getInstance(); $foo->incBar(); var_dump($foo->getBar());
PHP 8
Все магические методы, за исключением __construct() , __destruct() и __clone() , ДОЛЖНЫ быть объявлены как public , в противном случае будет вызвана ошибка уровня E_WARNING.
До PHP 8.0.0 для магических методов __sleep() , __wakeup() , __serialize() , __unserialize() и __set_state() не выполнялась проверка.
Singleton (Одиночка)
Singleton – один из самых простых шаблонов для понимания. Основное назначение – гарантировать существование только одно экземпляра класса. Причиной обычно является следующее: требуется только один объект исходного класса и Вам необходимо, что бы объект был доступен в любом месте приложения, т.е. глобальный доступ.
В качестве примера может служить класс для хранения установочных параметров(Settings). Settings class – хороший пример шаблона Singleton, потому что его данные не измены (единственный путь изменения установочных параметров это редактирование файла установочных параметров) и часто используются в различных частях приложения. Кроме того, создание новых объектов класса Settings, где это необходимо, требует ресурсы, что расточительно, т.к. объекты будут идентичны.
Определение
Шаблон Singleton предполагает наличие статического метода для создания экземпляра класса, при обращении к которому возвращается ссылка на оригинальный объект.
Пример для PHP5
Пример для PHP5(без реализации конкретных методов класса Settings)
class Settings private $settings = array();
private static $_instance = null;
private function __construct() // приватный конструктор ограничивает реализацию getInstance ()
>
protected function __clone() // ограничивает клонирование объекта
>
static public function getInstance() if(is_null(self::$_instance))
self::$_instance = new self();
>
return self::$_instance;
>
public function import() // .
>
public function get() // .
>
>
Реализация шаблона Singleton
Ключoм реализации шаблона Singleton является статическая переменная, переменная чье значение остается неизменным при исполнении за ее приделами. Это позволяет сохранить объект оригинальным между вызовами статического метода Settings::getInstance(), и возвратить ссылку на него при каждом последующем вызове метода.
Имейте так же в виду, что конструктор, как правило, приватный. Что бы обеспечить использование всегда только одного объекта Settings мы должны ограничить доступ к конструктору, что бы при попытке создания нового объекта возникала ошибка. Так же следует иметь в виду, что данные ограничения не возможны в PHP4.
The singleton pattern::the good, the bad, and the ugly
We use the singleton pattern in order to restrict the number of instances that can be created from a resource consuming class to only one.
Resource consuming classes are classes that might slow down our website or cost money. For example:
- Some external service providers (APIs) charge money per each use.
- Some classes that detect mobile devices might slow down our website.
- Establishing a connection with a database is time consuming and slows down our app.
So, in all of these cases, it is a good idea to restrict the number of objects that we create from the expensive class to only one.
Use a singleton pattern to restrict the number of objects to only one!
The anatomy of a singleton pattern
Let’s start by understanding the structural characteristics of a class that obeys the singleton pattern:
- A private constructor is used to prevent the direct creation of objects from the class.
- The expensive process is performed within the private constructor.
- The only way to create an instance from the class is by using a static method that creates the object only if it wasn’t already created.
// General singleton class. class Singleton < // Hold the class instance. private static $instance = null; // The constructor is private // to prevent initiation with outer code. private function __construct() < // The expensive process (e.g.,db connection) goes here. > // The object is created from within the class itself // only if the class has no instance. public static function getInstance() < if (self::$instance == null) < self::$instance = new Singleton(); > return self::$instance; > >
Why is it a singleton?
Since we restrict the number of objects that can be created from a class to only one, we end up with all the variables pointing to the same, single object.
// All the variables point to the same object. $object1 = Singleton::getInstance(); $object2 = Singleton::getInstance(); $object3 = Singleton::getInstance();
Practical example::database class
Let’s demonstrate the singleton pattern with a class that establishes a database connection, and restricts the number of instances to only one.
// Singleton to connect db. class ConnectDb < // Hold the class instance. private static $instance = null; private $conn; private $host = 'localhost'; private $user = 'db user-name'; private $pass = 'db password'; private $name = 'db name'; // The db connection is established in the private constructor. private function __construct() < $this->conn = new PDO("mysql:host=host>; dbname=name>", $this->user,$this->pass, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'")); > public static function getInstance() < if(!self::$instance) < self::$instance = new ConnectDb(); > return self::$instance; > public function getConnection() < return $this->conn; > >
Since we use a class that checks if a connection already exists before it establishes a new one, it really doesn’t matter how many times we create a new object out of the class, we still get the same connection. To prove the point, let’s create three instances out of the class and var dump them.
$instance = ConnectDb::getInstance(); $conn = $instance->getConnection(); var_dump($conn); $instance = ConnectDb::getInstance(); $conn = $instance->getConnection(); var_dump($conn); $instance = ConnectDb::getInstance(); $conn = $instance->getConnection(); var_dump($conn);
The result is the same connection for the three instances.
Class that doesn’t use a singleton to contact the database
To understand the problem that the singleton pattern solves, let’s consider the following class that has no mechanism to check if a connection already exists before it establishes a new connection.
// Connect db without a singleton. class ConnectDbWOSingleton < private $conn; private $host = 'localhost'; private $user = 'db user-name'; private $pass = 'db password'; private $name = 'db name'; // Public constructor. public function __construct() < $this->conn = new PDO("mysql:host=host>; dbname=name>", $this->user,$this->pass, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'")); > public function getConnection() < return $this->conn; > >
Now, each time we create a new object, we also establish a new database connection.
$instance = new ConnectDbWOSingleton(); $conn = $instance->getConnection(); var_dump($conn); $instance = new ConnectDbWOSingleton(); $conn = $instance->getConnection(); var_dump($conn); $instance = new ConnectDbWOSingleton(); $conn = $instance->getConnection(); var_dump($conn);
This has implications for slowing down the system because each new connection with the database costs time.
I encourage you to run the code to see the result for yourself.
The singleton pattern::the good, the bad, and the ugly
The singleton pattern is probably the most infamous pattern to exist, and is considered an anti-pattern because it creates global variables that can be accessed and changed from anywhere in the code.
Yet, The use of the singleton pattern is justified in those cases where we want to restrict the number of instances that we create from a class in order to save the system resources. Such cases include data base connections as well as external APIs that devour our system resources.
Joseph Benharosh is a full stack web developer and the author of the eBook The essentials of object oriented PHP.