- Design Patterns in PHP 8: Singleton & Multiton
- Singleton
- Multiton
- 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
Design Patterns in PHP 8: Singleton & Multiton
Hello, fellow developers!π§πΌβπ» I want to talk about design patterns in php in the few next articles. And I really like how the language is progressing that’s why I’ll make examples with the last innovations of php 8.
Singleton
Sometimes we need only one instance of some a class in different code places. For example, when we make a web application, usually it needs to connect to the database and it will be a really good idea to create just one copy of the connection and use it in every file, class, function. The pattern that will help us with this — Singleton, is one of the most popular and easiest design patterns. Let’s see how we can implement classes a database connection and logging with one instance in all objects:
class Singleton protected static self|null $instance = null; final private function __construct()<> final protected function __clone()<> final protected function __wakeup()<> public static function getInstance(): static if (static::$instance === null) static::$instance = new static; > return static::$instance; > > class Database extends Singleton public function connect() // . > > class Logger extends Singleton private $connection; public function settings($connection = null) $this->connection = $connection ?? '/var/logs/filename.log'; > public function error(string $message) // . > public function warn(string $message) // . > >
Now we’ll use a logger with writing logs in a database table. For that, we need a connection to the db and set it by the settings method:
$db = Database::getInstance(); $db->connect(); $logger = Logger::getInstance(); $logger->settings($db); $logger->error('Something wrong');
Multiton
But what if we need more than just one instance of a logger because some messages must be written in a file, some need to be send by email? Or we can have other reasons. For that, we’ll use the Multiton pattern. Multiton looks really resembling the previous pattern but with an array of instances of a class.
class Multiton protected static array|null $instance = null; final private function __construct()<> final protected function __clone()<> final protected function __wakeup()<> public static function getInstance(int|string $key): self if (!array_key_exists($key, self::$instance)) self::$instance[$key] = new self; > return self::$instance[$key]; > > class Logger extends Multiton private array $settings; public function setSettings(array $settings) // . > public function error(string $message) // . > public function warn(string $message) // . > >
Let’s make two loggers with different settings for saving logs into files and database. I won’t describe the setter of settings and writer to file/database in details cause it isn’t important for the pattern.
$fileLogger = Logger::getInstance('file'); $fileLogger->setSettings([ //. ]); $fileLogger->error('Error text'); $dbLogger = Logger::getInstance('database'); $dbLogger->setSettings([ //. ]); $dbLogger->error('Error will write in Database');
P.S. If you found this article helpful and want to dive deeper into design patterns in PHP and TypeScript, I have some exciting news for you! I’m currently writing a book that extensively covers these topics. It’s packed with practical examples, clear explanations, and real-world scenarios where these patterns can be applied. The book is designed to help both beginners and experienced developers gain a solid understanding of design patterns and how to implement them in PHP and TypeScript. Whether you’re looking to brush up on your knowledge or learn something new, this book has got you covered. You can subscribe to my blog on dev.to and then you will receive a notification as soon as the book is ready. I can’t wait for you to read it and take your coding skills to the next level!
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.