Используйте класс Action вместо контроллера в Symfony

#symfony #action #symfony4

#symfony #Экшен #symfony4

Вопрос:

Я сторонник подхода с использованием класса Action вместо контроллера. Объяснение очень простое: очень часто Контроллер включает в себя множество действий, когда, следуя принципу внедрения зависимостей, мы должны передать все требуемые зависимости конструктору, и это создает ситуацию, когда у контроллера огромное количество зависимостей, но в определенный момент времени (например, запрос) мы используем только некоторые зависимости. Сложно поддерживать и тестировать этот спагетти-код.

Чтобы пояснить, я уже работал с этим подходом в Zend Framework 2, но там он называется Middleware. Я нашел нечто подобное в API-Platform, где они также используют Action class вместо Controller, но проблема в том, что я не знаю, как это приготовить.

UPD: Как я могу получить следующий класс Action и заменить стандартный контроллер и какую конфигурацию я должен добавить в обычный проект Symfony?

 <?php
declare(strict_types=1);

namespace AppActionProduct;

use AppEntityProduct;
use DoctrineORMEntityManager;
use SensioBundleFrameworkExtraBundleConfigurationMethod;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationResponse;
use SymfonyComponentRoutingAnnotationRoute;

class SoftDeleteAction
{
    /**
     * @var EntityManager
     */
    private $entityManager;

    /**
     * @param EntityManager $entityManager
     */
    public function __construct(EntityManager $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    /**
     * @Route(
     *     name="app_product_delete",
     *     path="products/{id}/delete"
     * )
     *
     * @Method("DELETE")
     *
     * @param Product $product
     *
     * @return Response
     */
    public function __invoke(Request $request, $id): Response
    {
        $product = $this->entityManager->find(Product::class, $id);
        $product->delete();
        $this->entityManager->flush();

        return new Response('', 204);
    }
}
  

Комментарии:

1. Такого рода вопросы, вероятно, лучше подходят для форума Reddit Symfony. Сказав это, действие — это просто контроллер с единственным методом действия. На самом деле ничего не нужно реализовывать. Вы могли бы рассмотреть возможность обновления вашего вопроса с более подробной информацией, прежде чем он будет закрыт.

2. Я взглянул на ваш связанный класс SoftDeleteAction. Я должен отметить, что вопросы только по ссылкам, как правило, вызывают такое же неодобрение, как и ответы только по ссылкам. В любом случае код должен работать «из коробки», за исключением внедрения объекта Product. Самое простое решение — ввести идентификатор, а затем использовать entity manager для его извлечения.

3. @Cerad Я отредактировал свой вопрос и добавил пример кода. Спасибо за ваши разъяснения.

4. Я подозреваю, что ваш маршрут не отслеживается? Из коробки обработчик аннотаций маршрута просматривает только каталог контроллера (config/routes/annotation.yaml). Самое простое решение — просто определить свой маршрут в config / routes.yaml. Поскольку вы используете метод __invoke, то вам просто нужен класс action для _controller. Метод=DELETE также может вызвать некоторые проблемы. На данный момент просто используйте POST, а затем проверьте документы о том, как подделать использование DELETE.

5. @Cerad Можете ли вы более подробно объяснить, как настраивать маршруты? Теперь у меня есть следующая конфигурация в моем app/config/routing.yml (см. Экран i.imgur.com/aRptcwd.png ) app_product: ресурс: «@AppProduct/Controller/» тип: префикс аннотации: /

Ответ №1:

Подход, который я пытался реализовать, называется ADR pattern (Action-Domain-Responder), и Symfony уже поддерживает это, начиная с версии 3.3. Вы можете ссылаться на него как на вызываемые контроллеры.

Из официальных документов:

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

 // src/Controller/Hello.php
use SymfonyComponentHttpFoundationResponse;
use SymfonyComponentRoutingAnnotationRoute;

/**
 * @Route("/hello/{name}", name="hello")
 */
class Hello
{
    public function __invoke($name = 'World')
    {
        return new Response(sprintf('Hello %s!', $name));
    }
}

  

Ответ №2:

Вопрос немного расплывчатый для stackoverflow, хотя он также немного интересен. Итак, вот некоторые детали настройки.

Начните с готового проекта-скелета S4:

 symfony new --version=lts s4api
cd s4api
bin/console --version # 4.4.11
composer require orm-pack

  

Добавьте SoftDeleteAction

 namespace AppActionProduct;
class SoftDeleteAction
{
    private $entityManager;

    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;
    }
    public function __invoke(Request $request, int $id) : Response
    {
        return new Response('Product ' . $id);
    }
}
  

И определите маршрут:

 # config/routes.yaml
app_product_delete:
    path: /products/{id}/delete
    controller: AppActionProductSoftDeleteAction

  

На этом этапе подключение почти завершено. Если вы перейдете по URL-адресу, вы получите:

 The controller for URI "/products/42/delete" is not callable:
  

Причина в том, что службы по умолчанию являются частными. Обычно вы расширяетесь от AbstractController, который заботится о том, чтобы сделать службу общедоступной, но в этом случае самый быстрый подход — просто пометить действие как контроллер:

 # config/services.yaml
    AppActionProductSoftDeleteAction:
        tags: ['controller.service_arguments']
  

На этом этапе у вас должно быть рабочее подключенное действие.

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

Вы также можете рассмотреть возможность добавления пустого интерфейса ControllerServiceArgumentsInterface, а затем с помощью функциональности services instanceof применить тег controller, чтобы вам больше не нужно было вручную определять свои службы контроллера.

Но этого должно быть достаточно, чтобы вы начали.