Что это такое
Паттерн Одиночка (Singleton) гарантирует, что у класса есть ровно один экземпляр во всей программе, и даёт всем одну и ту же точку доступа к нему. Типичные одиночки: конфигурация, пул соединений, логгер.
Инструмент PHP для этого — приватный конструктор плюс статический метод instance(): никто вне класса не может вызвать new, а единственная точка доступа создаёт экземпляр при первом вызове. Поскольку PHP выполняет каждый запрос в отдельном изолированном процессе, «один на всю программу» означает один на запрос — или один на запуск скрипта командной строки.
Пример: одна конфигурация для всех
Конфигурация загружается при первом вызове Config::instance() и больше никогда. Каждый вызывающий получает один и тот же объект.
Загрузка один раз за приватным конструктором
Строка «загружаем конфигурацию» печатается только один раз, хотя instance() вызывается дважды, — а $a === $b доказывает, что оба вызывающих держат один и тот же экземпляр. Оператор ??= создаёт объект только тогда, когда свойство всё ещё равно null.
Вывод: PHP-скрипт
<?php
class Config {
private static ?self $instance = null;
public string $appName;
// Приватный: никто вне класса не может вызвать new Config().
private function __construct() {
echo "загружаем конфигурацию (происходит один раз)\n";
$this->appName = "PromptCookbook";
}
public static function instance(): self {
return self::$instance ??= new self();
}
}
$a = Config::instance();
$b = Config::instance();
echo "приложение: ", $a->appName, "\n";
echo "тот же экземпляр: ", var_export($a === $b, true), "\n";
Когда его применять
Применяйте, когда чего-то действительно должно быть ровно одно: одна загруженная конфигурация, один пул соединений, один кэш — и когда создание дорого или дубликаты будут конфликтовать.
Откажитесь, когда значение можно просто создать в начале скрипта и передать тем, кому оно нужно. Открытая передача зависимостей понятнее глобального доступа — одиночка это последнее средство, а не первое.
Плюсы и минусы
Плюсы: гарантированно единственный экземпляр — приватный конструктор блокирует любой посторонний new; ленивое создание при первом использовании; одна очевидная точка доступа.
Минусы: это замаскированная глобальная переменная — скрытые зависимости усложняют чтение кода; тесты не могут легко подменить его заглушкой; чрезмерное применение ведёт к запутанным программам.