Управление жизненным циклом зависимостей
Библиотека предоставляет три типа жизненного цикла для зависимостей, что особенно полезно в долгоживущих процессах:
- Приложениях на RoadRunner или FrankenPHP
- Фоновых workers
- Консольных командах, обрабатывающих множество задач
Вы можете управлять жизненным циклом любых зависимостей: containers, params, alias и autowiring.
🔄 Типы жизненных циклов
1. Scoped (Область видимости) ⭐ По умолчанию
- Зависимость создаётся один раз в пределах одного контейнера
- При создании нового экземпляра контейнера создаётся новая копия зависимости
- Идеально для обработки отдельных запросов
2. Singleton (Одиночка)
- Зависимость создаётся один раз на всё время выполнения скрипта
- Все экземпляры контейнера получают один и тот же объект
- Используется через параметр singletons в конфигурации
3. Factory (Фабрика)
- Каждый запрос создаёт новый экземпляр зависимости
- Используется через параметр factories в конфигурации
📋 Конфигурация
<?php
declare(strict_types=1);
namespace App;
class Scoped {}
class Singleton {}
class Factory {}
new \Cekta\DI\Compiler(
containers: [
Scoped::class,
Singleton::class,
Factory::class,
],
fqcn: 'App\\Runtime\\Container',
singletons: [Singleton::class], // Singleton-зависимости
factories: [Factory::class], // Factory-зависимости
// Scoped-зависимости не указываются явно (используются по умолчанию)
)->compile();
🧪 Пример использования
<?php
declare(strict_types=1);
namespace App;
function testLifecycle(string $className) {
$container1 = new \App\Runtime\Container();
$container2 = new \App\Runtime\Container();
$a = $container1->get($className);
$b = $container1->get($className); // Второй запрос к тому же контейнеру
$c = $container2->get($className); // Запрос к другому контейнеру
echo "Внутри одного контейнера: " . ($a === $b ? "одинаковый" : "разный") . "\n";
echo "Между разными контейнерами: " . ($a === $c ? "одинаковый" : "разный") . "\n";
echo "---\n";
}
echo "Scoped (по умолчанию):\n";
testLifecycle(Scoped::class);
echo "Singleton:\n";
testLifecycle(Singleton::class);
echo "Factory:\n";
testLifecycle(Factory::class);
Результат:
Scoped (по умолчанию):
Внутри одного контейнера: одинаковый
Между разными контейнерами: разный
---
Singleton:
Внутри одного контейнера: одинаковый
Между разными контейнерами: одинаковый
---
Factory:
Внутри одного контейнера: разный
Между разными контейнерами: разный
🎯 Сравнение жизненных циклов
| Тип | Внутри одного контейнера | Между разными контейнерами | Когда использовать |
|---|---|---|---|
| Scoped ⭐ | Один объект | Разные объекты | Обработка запросов, пользовательские сессии |
| Singleton | Один объект | Один объект | Конфигурация, подключения к БД, кеши |
| Factory | Разные объекты | Разные объекты | Stateless-сервисы, DTO, временные данные |
⚠️ Важные замечания
- Scoped по умолчанию - если не указан другой тип, используется Scoped
- Конфликты приоритетов - нельзя указать один класс одновременно как Singleton и Factory
- Производительность - Factory создаёт наибольшую нагрузку, Singleton - наименьшую
- Потокобезопасность - Singleton должен быть потокобезопасным в многопоточных средах
🚀 Пример для долгоживущего приложения
<?php
new \Cekta\DI\Compiler(
containers: [
HttpController::class,
UserRepository::class,
EmailService::class,
],
singletons: [
Database::class, // Одно подключение
RedisCache::class, // Общий кеш
Config::class, // Конфигурация
],
factories: [
HttpRequest::class, // Новый для каждого запроса
UserSession::class, // Новый для каждого пользователя
],
);
Правильное управление жизненным циклом позволяет:
- Экономить ресурсы (Singleton)
- Изолировать данные (Scoped)
- Предотвращать утечки памяти (Factory)
- Легко масштабировать приложение