Singleton

cyborg control its clones, opp singleton
cyborg control its clones, opp singleton
cyborg control its clones, opp singleton

Singleton – ensure that a class has only one instance,
while providing a global access point to this instance

<?php

class Logger
{
    private static ?Logger $uniqueInstance = null;
    private array $logs = [];

    // prevents direct instantiation
    private function __construct() {}

    // no one outside the class can duplicate the instance.
    private function __clone() {}

    /* __wakeup must be public (by PHP’s design), 
    since it’s automatically called during unserialize().*/
    public function __wakeup()
    {
        throw new \Exception("Cannot unserialize a singleton.");
    }

    /* Lazy construction / initialization - returns the single instance 
    of this class*/
    public static function getInstance(): Logger
    {
        if (self::$uniqueInstance === null) {
            self::$uniqueInstance = new Logger();
        }
        return self::$uniqueInstance;
    }

    // Methods
    public function addLog(string $message): void
    {
        $this->logs[] = $message;
    }

    public function showLogs(): void
    {
        foreach ($this->logs as $log) {
            echo $log . PHP_EOL;
        }
    }
}

// Usage exemple
$logger1 = Logger::getInstance();
$logger2 = Logger::getInstance();

$logger1->addLog("First log entry.");
$logger2->addLog("Second log entry.");

$logger1->showLogs();

//Checking the solid proof...

if ($logger1 === $logger2) {
    echo "Same instance" . PHP_EOL;
} else {
    echo "Different instances" . PHP_EOL;
}

echo "Logger1 ID: " . spl_object_id($logger1) . PHP_EOL;
echo "Logger2 ID: " . spl_object_id($logger2) . PHP_EOL;

/*
Output:

First log entry.
Second log entry.
Same instance
Logger1 ID: 1
Logger2 ID: 1
*/

The code above works perfectly for our purpose, but it’s not inheritance-safe, meaning it always creates a Logger instance, even if called from a subclass. Therefore:

class FileLogger extends Logger {}
FileLogger::getInstance(); //still returns the instance of Logger, not a instance of FileLogger

If we want that each subclass has its own singleton, we must change this behavior by modifying the getInstance() method, like this:

public static function getInstance(): Logger 
{
    $className = static::class;
    if (!isset(self::$uniqueInstance[$className])) {
        self::$uniqueInstance[$className] = new static();
    }
    return self::$uniqueInstance[$className];
}

Comparison:

FeatureVersion
self
Version
static::class
Supports inheritance❌ No✅ Yes
Each subclass has its own singleton❌ No✅ Yes
Refers to who?Refer to the class where the code was originally defined.Refer to the class that was actually called at runtime.
When use?You want a single, strict singleton for only the original class.You expect inheritance or extensions of your original class.

When Singleton pattern is useful?

  • Logger – Keep a single instance that writes all logs from different parts of the app.
  • Configuration Manager – Store settings (like API keys, database credentials, etc.) in one place.
  • Database Connection – Maintain one connection to the database.
  • Cache Manager – Keep a single cache handler (like Redis, Memcached, or file cache)
  • Session Manager – Handle user session data globally.
  • Event Dispatcher or Queue Manager – Centralize how events or background tasks are managed.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *