Урок 11: Сервисы и Dependency Injection в Symfony

В 11-ом уроке мы разберем одну из ключевых концепций Symfony, это сервисы и Dependency Injection (DI). Если вы дошли до этого урока, то уже знакомы с основами Symfony: «routing, контроллеры, шаблоны». Теперь пришло время углубиться в архитектуру приложений и понять, как Symfony управляет зависимостями.

Этот урок будет насыщен практикой, мы создадим кастомные сервисы, настроим автовайринг и разберемся с конфигурацией сервисов. В конце вас ждут задания для закрепления материала.

Что такое сервисный контейнер?

Сервисный контейнер это «мозг» Symfony, который управляет всеми объектами (сервисами) вашего приложения. Представьте его как фабрику: вы говорите, какой сервис нужен, а контейнер создает его, автоматически подставляя все зависимости.

Основные принципы:

  1. Сервис это любой PHP-объект, выполняющий определенную задачу (например, отправка email или работа с базой данных).
  2. Dependency Injection (DI) это паттерн, при котором зависимости (другие сервисы) передаются объекту извне, а не создаются внутри него.
  3. Контейнер хранит инструкции по созданию сервисов и их зависимостей.

Пример без DI:

php
class NotificationController extends AbstractController  
{  
    public function send(): Response  
    {  
        // Плохо: зависимость создается внутри класса  
        $emailSender = new EmailSender();  
        $emailSender->send('Hello!');  
        return new Response('OK');  
    }  
}

Если EmailSender потребует изменения (например другой способ отправки), придется править все места, где он создается.

Пример с DI:

php
class NotificationController extends AbstractController  
{  
    public function send(EmailSender $emailSender): Response  
    {  
        // Хорошо: зависимость внедряется извне  
        $emailSender->send('Hello!');  
        return new Response('OK');  
    }  
}

Теперь EmailSender управляется контейнером и его легко заменить или модифицировать.

Создание кастомных сервисов

Давайте создадим свой сервис для работы с уведомлениями.

Шаг 1: Создаем класс сервиса

Создайте файл src/Service/NotificationService.php:

php
namespace App\Service;  

class NotificationService  
{  
    public function sendNotification(string $message): string  
    {  
        // Логика отправки уведомления  
        return "Уведомление отправлено: $message";  
    }  
}

Шаг 2: Регистрация сервиса в контейнере

Symfony автоматически регистрирует классы в директории src/ как сервисы благодаря настройкам в config/services.yaml:

yaml
services:  
    _defaults:  
        autowire: true  
        autoconfigure: true  

    App\:  
        resource: '../src/'  
        exclude:  
            - '../src/DependencyInjection/'  
            - '../src/Entity/'  
            - '../src/Kernel.php'

Теперь сервис можно внедрить в контроллер:

php
use App\Service\NotificationService;  

class NotificationController extends AbstractController  
{  
    public function send(NotificationService $notificationService): Response  
    {  
        $result = $notificationService->sendNotification('Новое сообщение');  
        return new Response($result);  
    }  
}

Автовайринг (Autowiring)

Автовайринг это механизм Symfony, который автоматически определяет зависимости сервиса по типам аргументов.

Пример с зависимостями

Допустим, наш NotificationService требует логгер. Создадим сервис LoggerService:

php
// src/Service/LoggerService.php  
namespace App\Service;  

class LoggerService  
{  
    public function log(string $message): void  
    {  
        file_put_contents('log.txt', $message . PHP_EOL, FILE_APPEND);  
    }  
}

Модифицируем NotificationService:

php
use App\Service\LoggerService;  

class NotificationService  
{  
    private LoggerService $logger;  

    public function __construct(LoggerService $logger)  
    {  
        $this->logger = $logger;  
    }  

    public function sendNotification(string $message): string  
    {  
        $this->logger->log("Отправлено: $message");  
        return "Уведомление отправлено: $message";  
    }  
}

Как это работает:

  1. Symfony видит, что NotificationService требует LoggerService в конструкторе.
  2. Контейнер находит сервис LoggerService (он уже зарегистрирован благодаря автовайрингу).
  3. LoggerService автоматически передается в NotificationService.

Конфигурация сервисов в services.yaml

Файл config/services.yaml позволяет тонко настраивать сервисы.

Базовая структура:

yaml
services:  
    _defaults:  
        autowire: true    # Включить автовайринг  
        autoconfigure: true # Автоматически назначать теги  

    App\:  
        resource: '../src/'  
        exclude:  
            - '../src/Entity/'

Ручная регистрация сервиса

Иногда нужно явно указать сервис. Например, для стороннего класса:

yaml
services:  
    app.custom_service:  
        class: App\Service\CustomService  
        arguments:  
            - '@app.another_service' # Внедрение зависимости по ID  

Аргументы сервиса

Можно передавать параметры:

yaml
parameters:  
    app.notification_email: 'admin@example.com'  

services:  
    App\Service\NotificationService:  
        arguments:  
            $email: '%app.notification_email%'

Изменим конструктор NotificationService:

php
class NotificationService  
{  
    private string $email;  

    public function __construct(LoggerService $logger, string $email)  
    {  
        $this->logger = $logger;  
        $this->email = $email;  
    }  
}

Практические задачи

Задача 1: Создание сервиса для генерации случайных чисел

  1. Создайте класс RandomNumberGenerator в src/Service/.
  2. Добавьте метод generate(int $min, int $max): int.
  3. Внедрите сервис в контроллер и выведите случайное число.

Решение:

php
// src/Service/RandomNumberGenerator.php  
namespace App\Service;  

class RandomNumberGenerator  
{  
    public function generate(int $min, int $max): int  
    {  
        return rand($min, $max);  
    }  
}
php
// В контроллере  
public function number(RandomNumberGenerator $generator): Response  
{  
    $number = $generator->generate(1, 100);  
    return new Response("Случайное число: $number");  
}

Задача 2: Настройка сервиса с параметрами

  1. Объявите параметр app.default_min и app.default_max в services.yaml.
  2. Передайте их в RandomNumberGenerator.
  3. Используйте значения по умолчанию, если аргументы не переданы.

Решение:

yaml
parameters:  
    app.default_min: 1  
    app.default_max: 100  

services:  
    App\Service\RandomNumberGenerator:  
        arguments:  
            $min: '%app.default_min%'  
            $max: '%app.default_max%'
php
class RandomNumberGenerator  
{  
    private int $min;  
    private int $max;  

    public function __construct(int $min, int $max)  
    {  
        $this->min = $min;  
        $this->max = $max;  
    }  

    public function generate(?int $min = null, ?int $max = null): int  
    {  
        $min ??= $this->min;  
        $max ??= $this->max;  
        return rand($min, $max);  
    }  
}

Задача 3: Тегирование сервисов

Создайте сервис, который автоматически добавляется в цепочку обработки событий.

  1. Добавьте интерфейс MessageHandlerInterface.
  2. Зарегистрируйте сервис с тегом kernel.event_listener.
yaml
services:  
    App\EventListener\CustomListener:  
        tags:  
            - { name: kernel.event_listener, event: kernel.request }

Сегодня вы узнали:

  1. Как работает сервисный контейнер.
  2. Как создавать и использовать кастомные сервисы.
  3. Что такое автовайринг и как он упрощает разработку.
  4. Как конфигурировать сервисы через services.yaml.

Главный плюс Symfony, это гибкость. Вы можете комбинировать автоматическую и ручную настройку, чтобы создавать мощные приложения без лишнего кода.

Хотите изучить Symfony от А до Я? Полный курс по Symfony для начинающих

Поделиться статьей:
Поддержать автора блога

Поддержка автора осуществляется с помощью специальной формы ниже, предоставленной сервисом «ЮMoney». Все платёжные операции выполняются на защищённой странице сервиса, что обеспечивает их корректность и полную безопасность.

Персональные рекомендации
Оставить комментарий