How It Works
Implementation Details
The Singleton trait provides a complete singleton implementation through several key mechanisms:
Instance Storage
The trait uses a static local variable within the getInstance() method to store instances:
public static function getInstance(): static
{
static $instances;
$calledClass = get_called_class();
if (!isset($instances[$calledClass])) {
$instances[$calledClass] = new $calledClass();
}
return $instances[$calledClass];
}
This approach allows the trait to manage separate instances for each class that uses it. The $instances array is keyed by the class name, ensuring that each class gets its own singleton instance.
Protected Constructor
The trait provides a protected constructor:
protected function __construct()
{
// Constructor cannot be public
}
Classes using the trait should override this with their own private or protected constructor to prevent direct instantiation.
Clone Prevention
The __clone() magic method is overridden to prevent cloning:
public function __clone()
{
throw new SingletonException('You can not clone a singleton.');
}
Serialization Prevention
The __sleep() magic method prevents serialization:
public function __sleep()
{
throw new SingletonException('You can not serialize a singleton.');
}
Unserialization Prevention
The __wakeup() magic method prevents unserialization:
public function __wakeup()
{
throw new SingletonException('You can not deserialize a singleton.');
}
Why Use a Trait?
Using a trait for the Singleton pattern offers several advantages:
- Reusability - Apply the pattern to any class without inheritance
- No Base Class Required - Your singleton can extend other classes if needed
- Clean Separation - Pattern implementation is separate from business logic
- Type Safety - Returns
statictype for proper IDE support
Class Hierarchy Support
Because this implementation uses a trait rather than a base class, your singleton can extend other classes:
class BaseService
{
protected function log(string $message): void
{
// Logging logic
}
}
class MyService extends BaseService
{
use \ByJG\DesignPattern\Singleton;
private function __construct()
{
$this->log("Service initialized");
}
}
Multiple Singleton Classes
Each class that uses the Singleton trait gets its own independent instance:
class ConfigManager
{
use \ByJG\DesignPattern\Singleton;
private function __construct() {}
}
class DatabaseManager
{
use \ByJG\DesignPattern\Singleton;
private function __construct() {}
}
// These are completely separate instances
$config = ConfigManager::getInstance();
$db = DatabaseManager::getInstance();
Limitations
The singleton pattern as implemented does not support constructor arguments. This is by design - the first call to getInstance() has no arguments to pass. If you need configuration, use setter methods after getting the instance.
The instance is created on the first call to getInstance(). There's no mechanism to delay instantiation further or pre-initialize instances.