Singleton
The Singleton pattern is used to ensure that a single instance of an object exists throughout code.
When to use the Singleton pattern
Common use cases for singletons are:
- Database connections (e.g. an instance of PDO).
- Templates (e.g. Smarty).
A singleton is useful when you want to have a consistent setup across your application. For example, if you have a templating engine and want to inject the current session data in order to decide whether to show login / logout links.
A singleton can be seen as similar to a global variable that is only initialised once, the first time it is accessed. There are a few advantages of using a singleton over a global variable:
- If the initialisation process is expensive, it is only performed once, and only when the singleton is accessed for the first time (which might be never in some parts of the application).
- Everything uses exactly the same initialisation.
- No other code can change the configuration.
However, a singleton does still have some of the downsides of a global variable, such as coupling your code to the singleton, which can make it harder to write tests. The coupling can be reduced somewhat by injecting the singleton instance into your classes, instead of calling it within them. For example:
<?php declare(strict_types=1); class MyClass { private PDO $database; public function __construct(PDO $database) { $this->database = $database; } } $myObject = new MyClass(PDOConnection::getInstance());
Instead of:
<?php declare(strict_types=1); class MyClass { private PDO $database; public function __construct() { $this->database = PDOConnection::getInstance(); } } $myObject = new MyClass();
For tests, you may want a slightly different singleton, e.g. one which mocks a database connection. This could be achieved by subclassing the singleton, or writing a separate class for testing.
Sample implementation
<?php declare(strict_types=1); namespace App; use PDO; class PDOConnection { private static $instance = null; private function __construct() { } public static function getInstance(): PDO { if (self::$instance === null) { self::$instance = new PDO( 'mysql:host=' . DB_SERVER . ';dbname=' . DB_DATABASE, DB_USER, DB_PASSWORD, [ PDO::ATTR_PERSISTENT => true, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ] ); } return self::$instance; } }
The constructor is private as we do not want to create instances of this class using the new
operator - everything must go through getInstance
.