Как определять контроллеры как сервисы

Дата обновления перевода 2023-09-01

Как определять контроллеры как сервисы

В Symfony контроллер не должен быть зарегистрирован как сервис. Но если вы используете конфигурацию services.yaml по умолчанию , и ваши контроллеры расширяют класс AbstractController, они уже автоматически зарегистрированы, как сервисы. Это означает, что вы можете использовать внедрение зависимостей, как в любой другой нормальный сервис.

Если ваши контроллеры не расширяют класс AbstractController, вы должны ясно пометить ваши сервисы контроллера как public. Как вариант, вы можете применить тег controller.service_arguments к вашим сервисам контроллера. Это сделает тегированные сервисы public и позволит вам внедрять сервисы в параметрах метода:

1
2
3
4
5
6
7
# config/services.yaml

# контроллеры импортируются отдельно, чтобы гарантировать, что сервисы могут быть внедрены
# в качестве аргумента действия, даже если вы не расширяете никакой базовый класс контроллера
App\Controller\:
   resource: '../src/Controller/'
   tags: ['controller.service_arguments']

Note

Если вы не используете ни автомонтирование, ни
автоконфигурацию и вы расширяете AbstractController, вам нужно будет применить другие теги и сделать несколько вызовов методов для регистрации
контроллеров в качестве сервисов:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# config/services.yaml

# эта расширенная конфигурация необходима только если не используется автомонтирование/автоконфигурация,
# что не распространено и не рекомендуется

abstract_controller.locator:
    class: Symfony\Component\DependencyInjection\ServiceLocator
    arguments:
        -
            router: '@router'
            request_stack: '@request_stack'
            http_kernel: '@http_kernel'
            session: '@session'
            parameter_bag: '@parameter_bag'
            # здесь вы можете добавить больше сервисов по мере необходимости (например, сервис `serializer`)
            # и посмотреть на класс AbstractController, чтобы увидеть, какие сервисы
            # определены в локаторе

App\Controller\:
    resource: '../src/Controller/'
    tags: ['controller.service_arguments']
    calls:
        - [setContainer, ['@abstract_controller.locator']]

По желанию, вы можете использовать PHP-атрибут #[AsController], чтобы автоматически пприменить тег controller.service_arguments к вашим сервисам контроллера:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/Controller/HelloController.php
namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\Routing\Attribute\Route;

#[AsController]
class HelloController
{
    #[Route('/hello', name: 'hello', methods: ['GET'])]
    public function index(): Response
    {
        // ...
    }
}

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

Используйте синтаксис service_id::method_name, чтобы ссылаться на метод контроллера. Если id сервиса являвется полностью квалифицированными именем класса (FQCN) вашего контроллера, как рекомендует Symfony, то синтаксис будет таким же, как если бы контроллер не был сервисом, вроде: App\Controller\HelloController::index:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/Controller/HelloController.php
namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class HelloController
{
    #[Route('/hello', name: 'hello', methods: ['GET'])]
    public function index(): Response
    {
        // ...
    }
}

Вызываемые контроллеры

Контроллеры также могут определять одно действие, используя метод __invoke(), что является распространенной практикой при следовании паттерну ADR (Действие-Домен-Ответчик):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/Controller/Hello.php
namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

#[Route('/hello/{name}', name: 'hello')]
class Hello
{
    public function __invoke(string $name = 'World'): Response
    {
        return new Response(sprintf('Hello %s!', $name));
    }
}

Альтернативы базовых методов контроллера

При использовании контроллера, определённого как сервиса, вы всё ещё можете расширить базовый контроллер AbstractController , и использовать его сокращения. Но, вы не обязаны это делать! Вы можете выбрать не расширять ничего и использовать внедрение зависимоти, чтобы получить доступ к разным сервисам.

Базовый исходный код класса контроллера - это отличный способ увидеть, как можно добиться выполнения общих задач. Например, $this->render() обычно используется для отображения шаблона Twig и возвращения Ответа. Но, вы также можете сделать это напрямую:

В контроллере, определённом как сервис, вы можете вместо этого внедрить сервис twig и использовать его напрямую:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/Controller/HelloController.php
namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;
use Twig\Environment;

class HelloController
{
    public function __construct(
        private Environment $twig,
    ) {
    }

    public function index(string $name): Response
    {
        $content = $this->twig->render(
            'hello/index.html.twig',
            ['name' => $name]
        );

        return new Response($content);
    }
}

Вы также можете использовать специальное внедрение зависимости на основе действий , чтобы получаь сервисы в качестве аргументов ваших методов действий контроллера.

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

Наилучший способ увидеть, как заменить базовые воспомогательные методы Controller - это посмотреть на класс AbstractController, содержащий его логику.

Если вы хотите узнать, какое типизирование использовать для каждого сервиса, смотрите метод getSubscribedServices() в AbstractController.