PHP Singleton Pattern
A short screencast: the Singleton pattern in PHP — one object for the whole app with lazy initialization.
Overview
We build a Logger that cannot have more than one instance. The constructor is private, and getInstance() creates the object only on the first call, then always returns the same one. We write a message through $a, read it through $b — and it is the very same object.
UML diagram
Code
Logger.php
class Logger
{
private static ?Logger $instance = null;
public array $messages = [];
private function __construct() {}
public static function getInstance(): Logger
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function log(string $message): void
{
$this->messages[] = $message;
}
}
demo.php
require_once 'Logger.php';
$a = Logger::getInstance();
$b = Logger::getInstance();
$a->log('first');
echo $a === $b ? 'Same instance: true' : 'Same instance: false';
echo PHP_EOL;
echo 'Messages: ' . implode(', ', $b->messages);
Note: pros and criticism
The upside is a single access point and lazy creation: the instance uses no memory until it is actually needed. But we pay for it with global state — anyone can write to the log, which complicates tests and hides the dependency. In many cases, instead of a singleton, injecting one object from a container is a better choice, because the decision stays with the calling code.
Example output
Running php demo.php in the terminal produces:
Same instance: true
Messages: first
Advantages
- Guarantees a single instance per application.
- Lazy initialisation: the object is created on first use.
- A shared access point to a common resource.
- Saves memory and resources: one connection, one logger.
When to use
- When a class must have exactly one instance.
- When you need shared access to a resource (config, log, cache).
- When creating the resource is expensive and should be lazy.
- When global state must be controlled from one place.
Usage in Laravel
In Laravel the singleton is the service container: a ->singleton() binding guarantees that app() returns the same object every time. The Illuminate\Log\LogManager, Illuminate\Cache\CacheManager and the database connection are registered as singletons, while facades (Illuminate\Support\Facades\Log, Cache) provide static access to them.