Компонент DependencyInjection
Service Container -- ядро Symfony. Каждый объект в приложении (контроллер, репозиторий, сервис) управляется контейнером. Контейнер отвечает за создание объектов и внедрение их зависимостей.
Symfony 8.0: Компонент DependencyInjection теперь явно указан в списке тем экзамена. Убедитесь, что понимаете не только конфигурацию, но и сам компонент как отдельную библиотеку.
<?php
declare(strict_types=1);
// Standalone usage of DependencyInjection component
use Symfony\Component\DependencyInjection\ContainerBuilder;
$container = new ContainerBuilder();
// Register a service manually
$container->register('mailer', Mailer::class)
->addArgument('smtp://localhost');
$container->compile();
$mailer = $container->get('mailer');
Основные концепции
Сервис
Сервис -- любой PHP-объект, который выполняет определённую задачу. В Symfony практически всё является сервисом: логгер, mailer, entity manager.
<?php
declare(strict_types=1);
namespace App\Service;
final class InvoiceGenerator
{
public function __construct(
private readonly PdfRenderer $renderer,
private readonly MailerInterface $mailer,
) {
}
public function generateAndSend(Order $order): void
{
$pdf = $this->renderer->render($order);
$this->mailer->send($pdf, $order->getCustomerEmail());
}
}
Service Container (Контейнер сервисов)
Контейнер -- специальный объект, который:
- Создаёт сервисы (инстанцирование)
- Внедряет зависимости (injection)
- Управляет жизненным циклом (shared/non-shared)
- Компилирует конфигурацию для производительности
Жизненный цикл контейнера
| Этап | Описание |
|---|---|
| Загрузка конфигурации | Чтение YAML/PHP-файлов, атрибутов |
| Компиляция | Оптимизация, разрешение зависимостей |
| Дамп | Генерация PHP-класса контейнера |
| Использование | Получение сервисов из скомпилированного контейнера |
Регистрация сервисов
YAML-конфигурация
# config/services.yaml
services:
_defaults:
autowire: true
autoconfigure: true
# Автоматическая регистрация всех классов из директории
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
# Явная регистрация с аргументами
App\Service\InvoiceGenerator:
arguments:
$adminEmail: '%app.admin_email%'
PHP-атрибуты для регистрации
Symfony 8.0: PHP-атрибуты стали основным способом конфигурации сервисов. XML и аннотации удалены из экзамена. Остались только YAML и PHP-атрибуты.
<?php
declare(strict_types=1);
namespace App\Service;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
final class NewsletterManager
{
public function __construct(
private readonly MailerInterface $mailer,
#[Autowire('%app.newsletter_sender%')]
private readonly string $senderEmail,
#[Autowire('%kernel.debug%')]
private readonly bool $debug,
) {
}
}
Встроенные сервисы и параметры
Ключевые встроенные сервисы
| Сервис | ID | Описание |
|---|---|---|
| Logger | logger |
PSR-3 LoggerInterface |
| EventDispatcher | event_dispatcher |
Диспетчер событий |
| Router | router |
Маршрутизация |
| Request Stack | request_stack |
Текущий HTTP-запрос |
| Security | security.helper |
Проверка авторизации |
Встроенные параметры
# Доступны автоматически
kernel.project_dir: '/path/to/project'
kernel.environment: 'dev' # dev, test, prod
kernel.debug: true
kernel.cache_dir: '/path/to/var/cache/dev'
kernel.logs_dir: '/path/to/var/log'
kernel.bundles: { ... }
kernel.charset: 'UTF-8'
kernel.default_locale: 'en'
<?php
declare(strict_types=1);
namespace App\Service;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
final class FileUploader
{
public function __construct(
#[Autowire('%kernel.project_dir%/public/uploads')]
private readonly string $uploadDir,
#[Autowire('%kernel.environment%')]
private readonly string $environment,
) {
}
public function upload(UploadedFile $file): string
{
$filename = uniqid() . '.' . $file->guessExtension();
$file->move($this->uploadDir, $filename);
return $filename;
}
}
Конфигурация параметров
Определение параметров
# config/services.yaml
parameters:
app.admin_email: '[email protected]'
app.items_per_page: 20
app.supported_locales: ['en', 'fr', 'de']
# Environment variable binding
app.database_url: '%env(DATABASE_URL)%'
app.redis_port: '%env(int:REDIS_PORT)%'
app.feature_flags: '%env(json:FEATURE_FLAGS)%'
Процессоры переменных окружения
| Процессор | Пример | Описание |
|---|---|---|
string |
%env(string:VAR)% |
По умолчанию, строка |
bool |
%env(bool:VAR)% |
Приведение к boolean |
int |
%env(int:VAR)% |
Приведение к integer |
float |
%env(float:VAR)% |
Приведение к float |
json |
%env(json:VAR)% |
JSON-декодирование |
csv |
%env(csv:VAR)% |
CSV в массив |
file |
%env(file:VAR)% |
Чтение файла |
base64 |
%env(base64:VAR)% |
Base64-декодирование |
resolve |
%env(resolve:VAR)% |
Разрешение вложенных параметров |
<?php
declare(strict_types=1);
namespace App\Service;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
final class PaymentGateway
{
public function __construct(
// Environment variable processors
#[Autowire('%env(PAYMENT_API_KEY)%')]
private readonly string $apiKey,
#[Autowire('%env(bool:PAYMENT_SANDBOX)%')]
private readonly bool $sandbox,
#[Autowire('%env(int:PAYMENT_TIMEOUT)%')]
private readonly int $timeout,
) {
}
}
Shared и Non-shared сервисы
По умолчанию все сервисы shared (singleton) -- контейнер возвращает один и тот же экземпляр при каждом запросе.
services:
# Shared (default) -- один экземпляр
App\Service\Logger: ~
# Non-shared -- новый экземпляр при каждом запросе
App\Service\FormBuilder:
shared: false
<?php
declare(strict_types=1);
namespace App\Service;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
// Non-shared through attribute
#[\Symfony\Component\DependencyInjection\Attribute\Autoconfigure(shared: false)]
final class FormBuilder
{
private array $fields = [];
public function addField(string $name, string $type): self
{
$this->fields[$name] = $type;
return $this;
}
}
Подвох экзамена: По умолчанию сервисы являются shared (singleton). Если вопрос спрашивает, сколько раз создаётся сервис при нескольких вызовах
get()-- ответ 1 (один раз). Для non-shared сервисов -- каждый раз новый экземпляр.
Public и Private сервисы
services:
_defaults:
autowire: true
autoconfigure: true
public: false # По умолчанию private
# Явно public -- можно получить через $container->get()
App\Service\PaymentProcessor:
public: true
Подвох экзамена: С Symfony 4+ все сервисы по умолчанию private. Их нельзя получить через
$container->get()напрямую. Они доступны только через autowiring. Контроллеры -- исключение, так как они регистрируются как public черезAbstractController.
Alias сервисов
services:
# Alias -- когда интерфейс реализуется одним классом
App\Contract\PaymentInterface: '@App\Service\StripePayment'
# Autowiring alias
App\Service\StripePayment: ~
Psr\Log\LoggerInterface $paymentLogger: '@monolog.logger.payment'
<?php
declare(strict_types=1);
namespace App\Controller;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
final class PaymentController
{
public function __construct(
// Named autowiring -- will inject monolog.logger.payment
#[Autowire(service: 'monolog.logger.payment')]
private readonly LoggerInterface $paymentLogger,
) {
}
}
Итоги
- Service Container управляет созданием и зависимостями всех объектов
- Сервисы по умолчанию shared (singleton) и private
- Конфигурация через YAML и PHP-атрибуты (Sf8 -- без XML/аннотаций)
- Параметры поддерживают переменные окружения с процессорами
#[Autowire]позволяет точно указать, что внедрять