A Generic PHP Singleton
When we need a single instance of an object, of a specific class, to exist in our application, we use the Singleton pattern. Now, the pattern itself is frowned upon in some circles and is seen as an anti-pattern.
Some of the reasons the Singleton is seen as an anti-pattern are:
- It can be technical debt in disguise. The assumption that there will only be a need for one instance of the class during the lifetime of the application can be falsified in the future. This can lead to tedious refactoring.
- The introduction of global state into the application.
- The previous point can make unit testing hard if your singleton changes some global state.
In fact, the Singleton pattern does not respect the Single Responsibility Principle (SRP) since the required behavior of the class, say connecting to a database, is conflated with the code needed to ensure that only one instance exists at a time.
In order to solve these issues, it might be better to design the class with the properties and methods it needs to have, and thereafter design a mechanism to ensure that only one instance is in the system at a time. Below is the PHP code I use to do just that. Further down the article, I explain my approach.
The Code
You would use the function like this:
The Approach
The singletonize()
function takes as an argument a closure that is just a factory function for the original class:
$factoryOfDobClass = function () { return new ClassThatMarksDateOfInstantiation; };
That class has no concept of multiplicity, that is of how many of its objects should exist at a time. It only does what it needs to do.
We create a new anonymous class that will embody the singleton pattern and delegate all the method calls on it to the captured instance of the class of interest. Take notice of the use of the __call()
magic method to achieve the delegation:
public function __call($method, $args){return call_user_func_array([self::$instance, $method], $args);}
Moreover, I am not using the now idiomatic getInstance()
or instance()
methods typically used with singletons. If we are always going to get the same instance, maybe we shouldn’t have to say getInstance()
over and over again?
Making the __clone()
and wakeup()
magic methods private prevents:
- The cloning of a “singletonized” object.
- The de-serialization of an instance of the class through the global function
unserialize()
.
Singleton Pattern and Dependency Injection
You probably noticed that singletonize()
just returns an anonymous class. That is, we can’t use the original class directly as it does not contain logic to ensure that there is only one object. This means that a coder calling new
on the class of interest will get a new instance. That’s definitely not what we want. Dependency Injection (DI) comes to the rescue there. Stated simply, DI is an implementation of Inversion of Control. Or as read on Wikipedia:
[T]echnique whereby one object (or static method) supplies the dependencies of another object. A dependency is an object that can be used (a service). An injection is the passing of a dependency to a dependent object (a client) that would use it.
When injecting your dependencies rather than manually creating the needed instances yourself, you let a dependency container provide your classes constructors, their methods or their properties with the dependencies they need. A possible usage example would be:
$this->container->singletonize('DobClass', $this->factoryOfDobClass);
$dob = $this->container->get('DobClass');
Adhering to using a DI will help in avoiding inadvertently instantiating the original class. Not mention that DI fosters decoupling, but can also greatly help during testing. I elaborate more on DI, dependency containers and testing in a subsequent article.
Conclusion
The Singleton [anti-]pattern is one of the first we, as developers, learn. It is easy to understand. Despite the pattern’s issues with SRP violation, global state access or hidden technical debt, when properly understood the pattern can be valuable. Indeed, what singletonize()
gives us is the ability to separate multiplicity concerns from the behavior, hence responsibility, of the class. Used with dependency injection and singletonize()
, the Singleton pattern becomes a safe and valuable tool for the software engineer.