Ответ
Singleton — это порождающий паттерн проектирования, который гарантирует, что у класса существует только один экземпляр в течение всего жизненного цикла приложения, и предоставляет к нему глобальную точку доступа.
Классическая реализация на PHP включает:
- Приватный конструктор — чтобы предотвратить создание экземпляра оператором
newизвне класса. - Приватное статическое поле — для хранения единственного экземпляра.
- Публичный статический метод (обычно
getInstance()) — контролирует доступ к экземпляру: создает его при первом вызове и возвращает существующий при последующих. - Запрет клонирования и десериализации — для защиты от создания дубликатов.
Пример реализации:
class DatabaseConnection
{
private static ?self $instance = null;
private PDO $connection;
// 1. Приватный конструктор
private function __construct()
{
$this->connection = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
}
// 2. Статический метод для получения экземпляра
public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
// 3. Метод для получения реального соединения
public function getConnection(): PDO
{
return $this->connection;
}
// Запрещаем клонирование
private function __clone() {}
// Запрещаем десериализацию
public function __wakeup() { throw new Exception('Cannot unserialize a singleton.'); }
}
// Использование
$db1 = DatabaseConnection::getInstance();
$db2 = DatabaseConnection::getInstance();
var_dump($db1 === $db2); // true - это один и тот же объект
Важные замечания:
- Потокобезопасность: Базовая реализация не является потокобезопасной. В многопоточных средах (например, Swoole) требуется синхронизация.
- Антипаттерн: Singleton часто критикуют как антипаттерн, так как он вводит глобальное состояние, что усложняет модульное тестирование (затрудняет изоляцию зависимостей) и нарушает принцип единственной ответственности.
- Альтернативы: Во многих случаях лучше использовать Dependency Injection (DI), передавая единственный экземпляр в качестве зависимости через контейнер (например, в Symfony или Laravel), что сохраняет контроль над жизненным циклом, но делает зависимости явными и тестируемыми.