Singleton Design Pattern

Erland Muchasaj
7 min readFeb 13, 2024

--

In this article, we will talk about another creational design pattern with some examples and real-life analogies for a better understanding.

Singleton Design Pattern
Singleton Design Pattern

Definition

The Singleton Design Pattern is a creational design pattern that ensures a class has only one instance and provides a global point of access to that instance.

Problem

Suppose you’re developing an enterprise web application that interacts with an external API to retrieve product information, including details about prices, categories, and characteristics. In your application, you’ve created various classes like Product.php, Category.php, Characteristics.php, ProductPrice.php, each responsible for managing specific aspects of the product-related data.

As your application evolves, you realize that creating multiple instances of these classes could result in redundant calls to the external API, potentially leading to performance issues. Each class independently initiates connections to the API, fetching similar information, which is inefficient and resource-intensive.

Solution

To address this challenge and optimize resource utilization, you can implement the Singleton pattern. In this pattern, a single class instance is shared across the application, ensuring that only one connection to the external API is established. This way, redundant calls are minimized, and performance is improved.

The Singleton pattern ensures only one instance of the class, and all components share that same connection.

This pattern consists of 3 main components.

  1. Private or Protected Constructor: The Singleton class has a private constructor, preventing direct instantiation of the object from outside the class. Protected if you want to allow inheritance.
  2. Private Static Instance: The Singleton class holds a private static instance of itself, usually stored in a static variable.
  3. Public Static Method: It provides a public static method (often named getInstance) that allows the client code to access a single instance. If an instance does not exist, this method creates one; otherwise, it returns the existing instance.
Singleton Instances
Singleton Instances

A real-world analogy of the singleton is, to think of it as having a single key to your house. No matter how many family members you have, there’s only one key that grants access to your home. Similarly, the Singleton pattern ensures only one class instance (key) exists, and everyone accesses it through a common point.

Code

First, let's consider a class that is not Singleton.

So we have the Class ApiConnector that makes a request to an external API based on product SKU to retrieve information about that product.

require_once('PriceYuge.php'); // this might be a library that connect to external api

class ApiConnector
{
/**
* External API product
*/
private PriceYuge $product;

public function __construct()
{
echo __CLASS__ . " initialize."; // <= this for debug purposes only.
try {
$this->product = new PriceYuge('SHOALLEABLA42');
} catch (Exception $e) {
throw new Exception('Connection with external api failed.');
}
}

/**
* getData
* @return PriceYuge instance
*/
public function getData(): PriceYuge
{
return $this->product;
}
}

Now suppose that we call the ApiConnector class from different parts of our application during the lifetime of a request.

$connector1= new ApiConnector();
var_dump($connector1);

$connector2= new ApiConnector();
var_dump($connector2);

$connector3= new ApiConnector();
var_dump($connector3);

This will output:

ApiConnector initialize.
object(ApiConnector)[1] // <= pay close attention to this number

ApiConnector initialize.
object(ApiConnector)[3] // <= pay close attention to this number

ApiConnector initialize.
object(ApiConnector)[5] // <= pay close attention to this number

The numbers 1, 3, 5 represents instances of the class. As you can see they are different instances hence the string ApiConnector initialize. So each time we do new ApiConnector(), a new instance is created and therefore a new call to the API endpoint is made. This is very bad for our enterprise product because first of all we are making unnecessary calls and secondly we might hit a rate limit.

Don’t worry, we can fix it by implementing the Singleton pattern. As we said above, this pattern has at least 3 components (I will explain in a bit why I said at least 3), a private constructor, a private static instance, and a public static method.

We updated our code to implement such a pattern as follows:

require_once('PriceYuge.php');

class ApiConnector
{
/**
* Singleton Instance
*
* @var SingletonPatternExample
*/
private static $instance = null;

/**
* External API product
*/
private PriceYuge $product;

/**
* Disable instantiation.
*
* We can't use the constructor to create an instance of the class from outside.
* This is done to prevent multiple instance inicialisation.
* You need to obtain the instance from getInstance() method instead.
* Singleton's constructor should not be public. However, it can't be
* private either if we want to allow subclassing.
*
* @return void
*/
protected function __construct()
{
echo __CLASS__ . " initialize."; // <= this for debug purposes only.
try {
$this->product = new PriceYuge('SHOALLEABLA42');
} catch (Exception $e) {
throw new Exception('Connection with external api failed.');
}
}

/**
* Get the singleton instance
*
* @return SingletonPatternExample
*/
public static function getInstance()
{
if (!isset(self::$instance)) {
self::$instance = new self();
}

return self::$instance;
}

/**
* getData - A public methood to access privet properties of the class.
* @return PriceYuge instance
*/
public function getData(): PriceYuge
{
return $this->product;
}
}

Here we fulfilled all the above requirements for a class to be Singleton.

  • private/protected constructor
  • private static instance
  • public static method.

Now since this is a singleton, we can not create a new instance using the new keyword but using the static method instead.

// We have to call getInstance() static method
$singleton1 = ApiConnector::getInstance();
var_dump($singleton1);

$singleton2 = ApiConnector::getInstance();
var_dump($singleton2);

$singleton3 = ApiConnector::getInstance();
var_dump($singleton3);

The output will be:

ApiConnector initialize. // Class is instansiated only once.

object(ApiConnector)[1] // <= pay close attention to this number

object(ApiConnector)[1] // <= pay close attention to this number

object(ApiConnector)[1] // <= pay close attention to this number

As you can see from the example above, it doesn't matter how many times we initialize the class, it will always return the same instance [1]. we can confirm that by comparing instances:
var_dump($singleton1 === $singleton2); // true.

This is very good for our application because this means we are not hitting the endpoint each time we need a new instance of the API connector, thus saving bandwidth and increasing performance.

Gotcha

There are 2 tricky use cases when working with Singleton classes. As we explained in our PHP magic (php magic method), PHP has some methods that are invoked automatically during the lifecycle of a class.

  1. First the __clone() method is invoked when an object of a class is being cloned using clone keyword. This might violate the pattern’s intent, which is to ensure that only one instance of the class exists.
    To prevent cloning in a Singleton, you can override the __clone() method and throw an exception.
  2. Secondly the __wakeup() method is invoked when an object is being unserialized using unserialize() function, this creates a new instance of the class, you may want to ensure that even after deserialization, only one instance of the class exists.

Considering the example above let's see what happens when we don't handle these 2 use cases:

// We have to call getInstance() static method
$singleton1 = ApiConnector::getInstance();
var_dump($singleton1);

$singleton2 = ApiConnector::getInstance();
var_dump($singleton2);

$singleton3 = ApiConnector::getInstance();
var_dump($singleton3);

$singleton4 = clone $singleton1;
var_dump($singleton4);

$singletonSerialized = serialize($singleton1);
$singleton5 = unserialize($singletonSerialized);
var_dump($singleton5);
ApiConnector initialize. // Class is instansiated only once.

object(ApiConnector)[1]

object(ApiConnector)[1]

object(ApiConnector)[1]

object(ApiConnector)[3] // <= pay close attention to this number __clone()

object(ApiConnector)[4] // <= pay close attention to this number __wakeup()

As you can see when cloning and unserializing a class object a new instance is created, so for these use cases we can simply disable the user from cloning and serializing/unserializing the singleton class by overwriting those methods.


// continue of the code above

/**
* Disable the cloning of this class.
* Prevent the instance from being cloned (which would create a second instance of it)
*
* @return void
*/
public function __clone()
{
throw new Exception('Cloning on singleton classes is disabled.');
}

/**
* Disable the wakeup of this class.
* Prevent from being unserialized (which would create a second instance of it)
*
* @return void
*/
public function __wakeup()
{
throw new Exception('Unserialization on singleton classes is disabled.');
}

If we run the code above again $singleton4 = clone $singleton1; we will get a fatal error Fatal error: Uncaught Exception: Cloning on singleton classes is disabled..

Full example:

require_once('PriceYuge.php');

class ApiConnector
{
/**
* Singleton Instance
*
* @var SingletonPatternExample
*/
private static $instance = null;

/**
* External API product
*/
private PriceYuge $product;

/**
* Disable instantiation.
*
* We can't use the constructor to create an instance of the class from outside.
* This is done to prevent multiple instance inicialisation.
* You need to obtain the instance from getInstance() method instead.
* Singleton's constructor should not be public. However, it can't be
* private either if we want to allow subclassing.
*
* @return void
*/
protected function __construct()
{
echo __CLASS__ . " initialize.";
try {
$this->product = new PriceYuge('SHOALLEABLA42');
} catch (Exception $e) {
throw new Exception('Connection with external api failed.');
}
}

/**
* Get the singleton instance
*
* @return SingletonPatternExample
*/
public static function getInstance()
{
if (!isset(self::$instance)) {
self::$instance = new static();
}

return self::$instance;
}

/**
* Disable the cloning of this class.
* Prevent the instance from being cloned (which would create a second instance of it)
*
* @return void
*/
public function __clone()
{
throw new Exception('Cloning on singleton classes is disabled.');
}

/**
* Disable the wakeup of this class.
* Prevent from being unserialized (which would create a second instance of it)
*
* @return void
*/
public function __wakeup()
{
throw new Exception('Unserialization on singleton classes is disabled.');
}

/**
* getData
* @return PriceYuge instance
*/
public function getData(): PriceYuge
{
return $this->product;
}
}


// We have to call getInstance() static method
$singleton1 = ApiConnector::getInstance();
var_dump($singleton1);

$singleton2 = ApiConnector::getInstance();
var_dump($singleton2);

$singleton3 = ApiConnector::getInstance();
var_dump($singleton3);

// var_dump($singleton1 === $singleton2);

// $singleton4 = clone $singleton1;
// var_dump($singleton4);

// $singletonSerialized = serialize($singleton1);
// $singleton5 = unserialize($singletonSerialized);
// var_dump($singleton5);

// var_dump($singleton5->getData());

Singletons are classes that can only be instantiated once.
- They are inherently tightly coupled meaning they are difficult to test,
- They violate the Single Responsibility Principle by controlling their own creation and life cycle.

Feel free to Subscribe for more content like this 🔔, clap 👏🏻 , comment 💬, and share the article with anyone you’d like

And as it always has been, I appreciate your support, and thanks for reading.

--

--