-
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:
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.
- 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.
-
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={$this->host}; dbname={$this->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.