Ответ
Singleton гарантирует существование только одного экземпляра класса и предоставляет глобальную точку доступа к нему. В реальных проектах я использовал его для логгера или менеджера конфигурации, чтобы избежать дублирования ресурсов и состояния.
Пример: Логгер приложения В веб-приложении разные части системы (контроллеры, сервисы, middleware) должны писать логи в один и тот же файл или отправлять в одну и ту же внешнюю систему (например, Elasticsearch через Monolog). Создание нового экземпляра логгера для каждого вызова было бы избыточным и могло бы привести к конфликтам при записи в файл.
// app/Logging/AppLogger.php
class AppLogger
{
private static ?self $instance = null;
private MonologLogger $logger;
// Приватный конструктор предотвращает создание через `new`
private function __construct()
{
$this->logger = new MonologLogger('app');
$handler = new StreamHandler(storage_path('logs/app.log'), Level::Debug);
$this->logger->pushHandler($handler);
}
// Статический метод для получения единственного экземпляра
public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
// Публичные методы для логирования
public function info(string $message, array $context = []): void
{
$this->logger->info($message, $context);
}
public function error(string $message, array $context = []): void
{
$this->logger->error($message, $context);
}
}
// Использование в любом месте приложения
// app/Services/PaymentService.php
class PaymentService
{
public function process(Payment $payment): void
{
$logger = AppLogger::getInstance();
try {
// ... логика оплаты
$logger->info('Payment processed', ['payment_id' => $payment->id]);
} catch (Exception $e) {
$logger->error('Payment failed', ['error' => $e->getMessage()]);
throw $e;
}
}
}
Важно: В современных PHP-фреймворках (Laravel, Symfony) необходимость в «ручном» Singleton часто отпадает, так как контейнер внедрения зависимостей (DI Container) по умолчанию управляет сервисами в режиме singleton. Однако понимание паттерна важно для работы с legacy-кодом или при проектировании собственных библиотек.
Ответ 18+ 🔞
А, Singleton, ёпта! Ну это классика, как водка с пивом — все знают, что так делать нехорошо, но иногда без этого нихуя не получается.
Смотри, в чём суть-то, если на пальцах. Представь, что у тебя в приложении должен быть один-единственный главный по логам. Ну, типа дежурный по части, который всё записывает в одну толстую книгу жалоб и предложений. И вот представь, если бы каждый раз, когда какой-нибудь сервис захочет что-то логануть, он брал бы и заводил НОВУЮ такую книгу. Ебать мои старые костыли, да это же пиздец! Во-первых, бумаги на овердохуища, во-вторых, потом попробуй найди, кто и что писал — книг этих будет, как говна за баней.
Вот Singleton как раз и говорит: «Расслабься, чувак. Книга одна. Она тут, на полке. Хочешь писать — бери её, пиши и клади обратно. Всё».
Я вот на одном проекте так логгер и делал. Чтобы все модули, сервисы и прочая мартышлюшка не создавали свои инстансы, а тыкались в один, глобальный. Смотри, как это выглядело в коде, тут всё просто:
// app/Logging/AppLogger.php
class AppLogger
{
private static ?self $instance = null;
private MonologLogger $logger;
// Приватный конструктор — это ключ, ёпта! Он как замок на холодильнике от жадного соседа.
// Никаких `new AppLogger()` снаружи! Забудь.
private function __construct()
{
$this->logger = new MonologLogger('app');
$handler = new StreamHandler(storage_path('logs/app.log'), Level::Debug);
$this->logger->pushHandler($handler);
}
// А вот этот метод — единственная законная дверь.
// Стучись сюда, и тебе выдадут тот самый, единственный экземпляр.
public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
// Ну а тут уже обычные методы, чтобы инфу или ошибки писать.
public function info(string $message, array $context = []): void
{
$this->logger->info($message, $context);
}
public function error(string $message, array $context = []): void
{
$this->logger->error($message, $context);
}
}
И вот как этим пользовались:
// app/Services/PaymentService.php
class PaymentService
{
public function process(Payment $payment): void
{
// Взял логгер — один на всех, как общее полотенце в армии.
$logger = AppLogger::getInstance();
try {
// ... тут какая-то хитрая жопа с оплатой ...
$logger->info('Payment processed', ['payment_id' => $payment->id]);
} catch (Exception $e) {
// А если всё накрылось медным тазом — тоже пишем в ту же книгу!
$logger->error('Payment failed', ['error' => $e->getMessage()]);
throw $e;
}
}
}
Но! Важный момент, чувак. Сейчас-то, в этих ваших модных фреймворках, этот паттерн частенько — как хуй в пальто. Смотри сам: в том же Laravel или Symfony есть контейнер зависимостей. Ты ему говоришь: «Слушай, вот этот сервис у меня должен быть один на всё приложение». И он тебе его сам как синглтон и отдаёт, без всяких этих ручных getInstance(). Красота!
Так зачем тогда это знать? Да хуй с горы! Во-первых, в легаси-коде этого добра навалом, вротберунчик. Во-вторых, если пишешь какую-то свою, низкоуровневую библиотеку, без фреймворков — тут без синглтона бывает туго. А в-третьих, просто чтобы понимать, как эта магия под капотом работает, а не быть полупидором, который только кнопки нажимает.
Короче, инструмент как инструмент. Знать надо, применять с умом, а не тыкать везде, куда попало.