Сервисы и внедрение зависимостей
Сервис — объект, который выполняет полезную работу: отправляет письма, пишет в журнал, считает цены. Все сервисы живут в контейнере, и Symfony сам передаёт их туда, где они запрошены по типу. Так устроен весь фреймворк: EntityManagerInterface и репозитории из прошлых уроков — тоже сервисы.
Свой сервис
Любой класс в папке src уже сервис — регистрировать его не нужно:
// src/Service/PriceCalculator.php
namespace App\Service;
class PriceCalculator
{
public function withTax(float $price): float
{
return round($price * 1.2, 2);
}
}Получение сервиса
Достаточно указать тип в аргументах метода контроллера или в конструкторе:
use App\Service\PriceCalculator;
#[Route('/cart', name: 'cart')]
public function cart(PriceCalculator $calculator): Response
{
return new Response('Итого: ' . $calculator->withTax(1000));
}Зависимости сервиса
Сервис получает другие сервисы через конструктор:
namespace App\Service;
use Psr\Log\LoggerInterface;
use Symfony\Component\Mailer\MailerInterface;
class OrderNotifier
{
public function __construct(
private MailerInterface $mailer,
private LoggerInterface $logger,
) {}
public function notify(Order $order): void
{
$this->logger->info('Заказ оформлен', ['id' => $order->getId()]);
// отправка письма
}
}Это называют автоматическим связыванием: Symfony смотрит на типы и подставляет объекты.
Какие сервисы есть
php bin/console debug:autowiring
php bin/console debug:autowiring mailerКоманда показывает типы, которые можно запросить.
Настройки и переменные окружения
Строку или число по типу не подставить — их указывают атрибутом #[Autowire]:
use Symfony\Component\DependencyInjection\Attribute\Autowire;
class PaymentGateway
{
public function __construct(
#[Autowire(env: 'STRIPE_SECRET')]
private string $secret,
#[Autowire('%kernel.project_dir%/var/invoices')]
private string $invoiceDir,
) {}
}Переменные окружения записывают в .env, а секретные значения своего компьютера — в .env.local, который не попадает в репозиторий.
Интерфейс и реализация
Если интерфейс реализует один класс, Symfony подставит его сам. Когда реализаций несколько, нужную выбирают в config/services.yaml:
services:
App\Payment\PaymentGatewayInterface: '@App\Payment\StripeGateway'Частая ошибка
Сервис создаётся один раз и используется повторно. Если хранить в его свойствах данные конкретного запроса — текущего пользователя, накопленный список, — они «утекут» в следующий вызов. Сервис должен хранить только зависимости и настройки, а данные получать аргументами методов.