When I started writing PHP and learning my first OOP concepts and patterns, I remember being dazzled by the singleton pattern. I used it so much! I think I fell in love with its convenience. Just make something a singleton, and you can grab it and use it from anywhere you want.
I'm sure you know how they work already. The classical implementation is to make a static property and method called instance
, and if the property is not null, return it. Otherwise, create the instance and store it in the property, so then you can return it.
<?php
class Singleton
{
private static Singleton $instance = null;
public static function instance(): Singleton
{
if (self::$instance === null) {
self::$instance = new Singleton();
}
return self::$instance;
}
}
You can optionally make __clone
and __construct
private, to prevent more than one instance can exist.
Because You Can Doesn't Mean You Should
Sometimes we developers refer to code like that as globals. Is a short way of saying globally accessible. By globally we mean, from anywhere in the source code. The singleton above can be accessed from anywhere because of its static method. Convenient, isn't it?
My story with singletons is like the story when you find a really cool album or band, and put it on repeat forever, every day -- yes, I do that. It is good for a while, but that sentiment quickly evaporates and suddenly you don't find it as good anymore.
For me, that sentiment came when I started to learn about testing, and writing my first tests. I still remember how hard it was to write my first tests because I had made everything globally accessible. It was impossible to test the controller without a real database connection, third-party API calls and a queue. I gave up pretty quickly.
Eventually, you realise that when something is convenient in programming, it is so at the cost of making other things harder. Singletons make access to other routines in code very convenient, at the cost of an extreme coupling that makes it unbearably hard to test your code units in isolation.
Be careful. Some people like to promote things like these by proclaiming them as simple. Don't be deceived! In engineering, is very common to see simplicity disguised as her shallow cousin: convenience.
After a while, I gave up using globally accessible stuff in my code and moved on to better patterns, like Dependency Injection. Just because you can globally access something from anywhere does not mean you should.
Because You Shouldn't Doesn't Mean You Won't
But, here is the grain of salt. I do believe singletons and other kinds of globals have their place in a codebase. I think that place is the bootstrapping code.
You can call all singletons you like, as long as you pass them as arguments in the constructor for another class, for instance. In the code that bootstraps your application, you can access all kinds of globals and that is fine because the bootstrapping code is the code that is coupled to all the dependencies of your program. As long as those globals don't leak anything to the rest of the application code, you'll be perfectly fine.
I've found myself writing more Singletons than before because they are more convenient in bootstrapping code. As a library author, you can provide Singletons with sensible defaults for your users, while still giving them the possibility of bootstrapping their instance with custom values.
Overall, I think we need to get better at testing. Promoting testing everywhere will only do good to the PHP community because developers will quickly realise when they are writing hard-to-test code.