Pull to refresh

Comments 18

Возможности бандла:

Простой интерфейс контроллера — только один метод execute(Request $request): Response
Удобная генерация url по имени класса — $router->url(SomeEndpoint::class)
Удобство рефакторинга — при переименовании или перемещении контроллера не требуется дополнительных действий, например смена имени роута
Не нужно задавать имя роута (но можно при необходимости)


1. Давно есть в нативном symfony через `__invoke()` и controller as a service symfony.com/doc/current/controller/service.html
2. Как вы ниже пишете по тексту работает не всегда. Чаще всего это не работает, когда один и тот же контроллер доступен по нескольим роутам с разными дефолтными значениями, такие ситуации далеко не редкость. Мне кажется весьма неприятным попасть в ситуацию, когда придется полноситью переделать роутинг контроллера ради того, чтобы навесить на него еще один роут
3. Опять же, не всегда работает, т.к. не всегда работает п. 2
4. И снова не всегда работает.

Какие преимущества данного подхода над controller as a service?

Недостаток я вижу один и довольно существенный — вы привязываете контроллер не только к http-kernel, но и к кастомному роутингу и бандлу, что снижает возможность его переиспользования, особенно если вы пишите компонент, предоставляющий контроллеры, типа админок и всяких вьюеров

Основное преимущество — программная генерация роутов. Это важно, в частности, для наследования контроллеров. Но также полезно, если есть сложная логика генерации. В сторонних библиотеках этот бандо лучше не использовать.

Возможно статья не демонстрирует преимуществ при наследовании, не хватает примеров.

У меня есть подозрение, что функцию `routes` можно объявить как static, иначе создается иллюзия, что `routes` может возвращать разные значения в зависимости от runtime. Но это далеко не так из-за кэширования
Добавил пример с наследованием.
Теоретически можно сделать ее статической, но при генерации роутов могут понадобиться сторонние сервисы, например, для получения конфигов.

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

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

напрашивается закономерный вопрос о декомпозиции этой логики в отдельный сервис и построении загрузчика роутов вокруг нее

Согласен, надо подумать на этот счет, возможно, создать интерфейс для загрузчика, и в бандле получать все загрузчики, так же, как сейчас endpoint-ы.
Основная причина, почему routes() нестатический — чтобы при наследовании можно было использовать абстрактные методы.
Чтобы endpoint заработал, достаточно зарегистрировать его как сервис в symfony. Больше ничего делать не нужно — бандл сам обнаружит endpoint и добавит его роуты в общий список роутов приложения.

На этапе генерации кеша или в рантайме?

На этапе генерации кэша. Но теоретически можно брать конфигурацию из БД и на ее основе генерировать роуты (на целевом сервере). Правда, это все будет статически.
UFO landed and left these words here
А вот у меня вопрос, как решилась проблема с наследованием? Ведь сейчас все равно нужно переписывать весь метод routes() в дочернем методе копируя сигнатуру из родителя, то же самое что переписать аннотацию?
Проблема решается примерно так:
<?php
use Doctrine\ORM\EntityManagerInterface;
use PaLabs\EndpointBundle\EndpointInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Route;

abstract class BaseEntityListController implements EndpointInterface {
    
    protected $em;
    
    public function __construct(EntityManagerInterface $em)
    {
        $this->em = $em;
    }

    protected abstract function className(): string;
    
    public function routes()
    {
        return new Route(sprintf("/rest/%s/list", $this->sectionName()));
    }

    public function execute(Request $request): Response
    {
        // do work
        $this->em->getRepository($this->className())->findAll();
    }
    
    protected function sectionName(): string {
        $reflection = new \ReflectionClass($this->className());
        return $reflection->getShortName();
    }
}

class SomeEntityController extends BaseEntityListController {

    protected function className(): string
    {
        return MyEntity::class;
    }
}
А что Вы будете делать, если в вашем crud-приложении один из дочерних классов потребует специфическую реализацию? Вангую что Вы полезете в BaseEntityListController, и перенесёте кусок кода из уже немалого метода execute в отдельный метод, для того что бы в дальнейшем переопределить его в дочернем. А что если в дальнейшем появится ещё такая задача, и кусок кода будет в новосозданном методе, вы ещё и его дробить начнёте? Не много ли отвественности на базовом контроллере при Вашем подходе?
Все зависит от модели. Если логика сильно меняется от класса к классу, то возможно не стоит создавать базовый контроллер. Обычно хватает 2-3 методов в качестве точек расширения.
UFO landed and left these words here
Просьба владельцам хаба переименовать дескрипшн ведь Симфони 4 уже не на PHP5 а на PHP7.1 написана и строго требует его.
Only those users with full accounts are able to leave comments. Log in, please.