CreationalPHPverifiedVerified
Singleton Pattern in PHP
Ensures a class has only one instance and provides a global point of access to it.
How to Implement the Singleton Pattern in PHP
1Step 1: Define the Singleton class with a private constructor
class Singleton
{
private static ?self $instance = null;
private function __construct() {}
// Prevent cloning and unserialization
private function __clone() {}
public function __wakeup(): never
{
throw new \RuntimeException('Cannot unserialize singleton');
}2Step 2: Implement the static getInstance method
public static function getInstance(): static
{
if (static::$instance === null) {
static::$instance = new static();
}
return static::$instance;
}
// Business methods
public function doSomething(): void
{
echo "Singleton method called\n";
}
}3Step 3: Verify only one instance exists
$a = Singleton::getInstance();
$b = Singleton::getInstance();
var_dump($a === $b); // true<?php
declare(strict_types=1);
// [step] Define configuration and connection types
final readonly class DatabaseConfig
{
public function __construct(
public string $host = 'localhost',
public int $port = 5432,
public string $database = 'app',
public int $maxConnections = 10,
) {}
}
interface LoggerInterface
{
public function info(string $message, array $context = []): void;
public function error(string $message, array $context = []): void;
}
// [step] Implement the thread-safe database pool singleton
final class DatabasePool
{
private static ?self $instance = null;
/** @var array<int, array{id: int, active: bool}> */
private array $connections = [];
private bool $isShutdown = false;
private function __construct(
private readonly DatabaseConfig $config,
private readonly LoggerInterface $logger,
) {}
private function __clone() {}
public function __wakeup(): never
{
throw new \RuntimeException('Cannot unserialize singleton');
}
// [step] Lazy initialization with configuration
public static function getInstance(
?DatabaseConfig $config = null,
?LoggerInterface $logger = null,
): self {
if (self::$instance === null) {
$cfg = $config ?? new DatabaseConfig();
if ($logger === null) {
throw new \InvalidArgumentException('Logger required for first initialization');
}
self::$instance = new self($cfg, $logger);
self::$instance->initialize();
}
return self::$instance;
}
private function initialize(): void
{
for ($i = 0; $i < $this->config->maxConnections; $i++) {
$this->connections[$i] = ['id' => $i, 'active' => false];
}
$this->logger->info('Database pool initialized', [
'connections' => $this->config->maxConnections,
]);
}
/**
* @param list<mixed> $params
* @return list<array<string, mixed>>
*/
public function query(string $sql, array $params = []): array
{
if ($this->isShutdown) {
throw new \RuntimeException('Pool has been shut down');
}
$conn = $this->acquireConnection();
try {
// Execute query against connection pool
return [['sql' => $sql, 'params' => $params, 'result' => 'mock']];
} finally {
$this->releaseConnection($conn);
}
}
private function acquireConnection(): int
{
foreach ($this->connections as $id => &$conn) {
if (!$conn['active']) {
$conn['active'] = true;
return $id;
}
}
throw new \OverflowException('No available connections');
}
private function releaseConnection(int $id): void
{
if (isset($this->connections[$id])) {
$this->connections[$id]['active'] = false;
}
}
public function shutdown(): void
{
$this->isShutdown = true;
$this->connections = [];
self::$instance = null;
$this->logger->info('Database pool shut down');
}
/** Reset for testing */
public static function resetInstance(): void
{
self::$instance = null;
}
}Singleton Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Singleton Pattern in the Real World
“Think of a country’s president. There can only be one at any time. When anyone needs to communicate with the president, they don’t create a new one—they access the existing one through the official channel (the static method).”