Современный Symfony позволяет избавиться от горы YAML-конфигурации и управлять зависимостями прямо в коде с помощью PHP-атрибутов. Это делает контейнер зависимостей более очевидным, читаемым и легким в сопровождении.
Почему это важно
Раньше разработчики Symfony часто терялись между классами, YAML-файлами и тегами: почему сервис не внедряется? Почему не применяется тег? С PHP-атрибутами вся конфигурация сервиса может жить в одном месте прямо в классе. Это снижает когнитивную нагрузку и ускоряет разработку.
Базовая настройка: services.yaml
Прежде всего базовая конфигурация, которую всё равно оставляем в config/services.yaml:
services:
_defaults:
autowire: true
autoconfigure: true
App\:
resource: '../src/'
exclude: '../src/{Entity,Kernel.php}'
Эта настройка включает авто-внедрение зависимостей и автоматическую конфигурацию сервисов. Благодаря этому Symfony сам зарегистрирует все классы в src/ как сервисы, без явного указания в YAML.
Что такое Dependency Injection (DI)
DI - это принцип, при котором объект получает свои зависимости извне, а не создаёт их сам. Symfony-контейнер делает это автоматически:
class CreateProductService
{
public function __construct(private ProductRepository $repository) {}
}Если у вас включено автосканирование, Symfony сам найдёт ProductRepository и передаст его в конструктор.
Атрибуты для конфигурации сервиса
Атрибут #[Autowire] - внедрение переменных и сервисов
Вы можете внедрять не только другие сервисы, но и значения из .env или параметров:
use Symfony\Component\DependencyInjection\Attribute\Autowire;
public function __construct(
private MailerInterface $mailer,
#[Autowire(env: 'ADMIN_EMAIL')]
private string $adminEmail,
#[Autowire(env: 'EMAIL_FROM')]
private string $fromEmail
) {}Это заменяет старую необходимость определять параметры в YAML или XML и позволяет держать всё в одном месте.
Методовые зависимости: #[Required]
Если вам нужно внедрить зависимость через сеттер, а не через конструктор, можно пометить метод атрибутом #[Required]:
use Symfony\Contracts\Service\Attribute\Required;
#[Required]
public function setLogger(LoggerInterface $logger): void
{
$this->logger = $logger;
}Symfony вызовет этот метод и передаст нужный сервис, если он есть.
Теги сервисов с атрибутами
Symfony позволяет отмечать сервисы тегами с помощью атрибутов. Это полезно для расширяемых систем (например, обработчики, слушатели событий и пр.):
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
#[AutoconfigureTag('app.vendor')]
interface VendorInterface {}Теперь любой класс, реализующий этот интерфейс, автоматически получит тег app.vendor без YAML-конфигурации.
Внедрение всех сервисов по тегу
Чтобы получить все сервисы с определённым тегом, используйте аргумент с атрибутом:
use Symfony\Component\DependencyInjection\Attribute\AutowireIterator;
public function __construct(
#[AutowireIterator('app.vendor')]
iterable $vendors
) {}Теперь $vendors это коллекция всех сервисов, помеченных тегом app.vendor.
Декорирование сервисов
Если нужно изменить поведение сервиса, не меняя его код, Symfony поддерживает декораторный паттерн через атрибут #[AsDecorator]:
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated;
#[AsDecorator(decorates: VendorsHandler::class)]
class DiscountVendorHandler
{
public function __construct(
#[AutowireDecorated]
private VendorsHandler $inner
) {}
}Это говорит контейнеру заменить оригинальный сервис VendorsHandler на DiscountVendorHandler, передав оригинал как $inner.
Service Locator через атрибуты
Иногда нужно выбирать сервис динамически (например, в зависимости от ключа или расширения файла). Для таких случаев Symfony предлагает #[AutowireLocator]:
use Symfony\Component\DependencyInjection\Attribute\AutowireLocator;
public function __construct(
#[AutowireLocator([
'csv' => CsvImporter::class,
'json' => JsonImporter::class,
])]
private ContainerInterface $locators
) {}Это аналогично внедрению локатора, но без ручной конфигурации в YAML.
Что в итоге
Современные PHP-атрибуты позволяют:
Уменьшить конфигурацию в YAML.
Делать сервисы самодокументируемыми.
Избавиться от магии DI контейнера.
Писать более ясный, поддерживаемый и типобезопасный код.
Symfony остаётся мощным, но с атрибутами он стал ещё проще и приятнее.