Symfony: Factory контроллеров

Я создаю пользовательский комплект, позволяющий определять несколько типов пользователей, с их собственными репозиториями, менеджерами, поставщиками и т.д. Поэтому я решил вместо создания ограниченного набора контроллеров создать контроллерные заводы, которые будет создавать контроллеры на основе определенных типов пользователей и конфигурации. Но это поднимает важный вопрос: где и как должны работать эти заводы?

Теперь, разумеется, вам не нужно создавать контроллер в factory, мы также должны установить для него все маршруты.

Вопрос в том, какова была бы лучшая архитектура для этого?

Когда речь заходит о выборе слоя, где я буду размещать свой код, я рассматривал, в частности:

  • Определения загрузочных фабрик в методе Extension load и создание всех контроллеров там. Проблема: маршрутизатор там недоступен, потому что это происходит до создания контейнера, поэтому я не мог создавать маршруты в одном и том же месте.

  • Sooo... может быть, в компиляторе? Но пропуск компилятора не имеет доступа к конфигурации... Я имею в виду... на самом деле он имеет, если я просто загружу конфигурацию и обработаю ее вручную, но я все еще не уверен, что это хорошее место, но я Сейчас он склоняется к этому решению.

Когда дело доходит до создания маршрутов:

  • Должен ли я помещать логику создания маршрута в контроллер factory? Но я создаю контроллеры как службы, а factory не имеет доступа к serviceId созданного контроллера, а serviceId требуется для создания маршрута, поэтому нет.

  • В самом контроллере? Я имею в виду, как работают маршруты аннотаций, поэтому это может быть жизнеспособным. Контроллер должен будет реализовать что-то вроде моего собственного ControllerInterface с помощью метода getRoutes, а для внешнего сеанса службы/компилятора необходимо сначала создать контроллер как услугу, а затем получить маршруты от указанного контроллера, изменить их, они будут ссылаться на этот контроллер serviceId и добавлять их к маршрутизатору... независимо от того, насколько беспорядочно это выглядит.

  • Есть ли другой вариант?

Существует значительный недостаток информации об этом конкретном шаблоне - factory контроллеров:).

Ответ 1

Первая версия платформы API использовала подобный метод.

Первым шагом является регистрация маршрутов. Маршрут сопоставляет шаблон URL с контроллером, определенным в атрибуте маршрута _controller. Это как компонент маршрутизации и компоненты HttpKernel связаны между собой (нет сильной связи между этими двумя компонентами). Маршруты можно зарегистрировать, создав RouteLoader: http://symfony.com/doc/current/routing/custom_route_loader.html

Как работает API-платформа, Sonata и Easy Admin.

Во время выполнения будет выполняться вызов, указанный в атрибутах _controller. Он получит HTTP-запрос в параметре и должен вернуть HTTP-ответ. При необходимости он может получить доступ к другим службам (и даже к контейнеру).

Контроллер может быть любым вызываемым (метод, функция, invokable class...), но он также может быть сервисом благодаря следующему синтаксису my_controller_service:myAction (см. http://symfony.com/doc/current/controller/service.html).

Компонент DependencyInjection позволяет создавать службы с помощью factory: http://symfony.com/doc/current/service_container/factories.html. Factory может принимать другие сервисы или параметры (config).

Подводя итог:

1/Зарегистрируйте определение службы для своего контроллера, используя Factory, чтобы создать его, например:

# app/config/services.yml
services:
    # ...

    app.controller_factory:
        class: AppBundle\Controller\ControllerFactory
        arguments: ['@some_service', '%some_parameter%]

    app.my_controller:
        class:     AppBundle\Controller\ControllerInterface
        factory:   'app.controller_factory:createController'
        arguments: ['@some_service', '%some_parameter%]

Конечно, если вам нужно, создайте свои определения контроллеров программным путем в классе AppBundle\DependencyInjection\AppBundleExtension. Вы также можете использовать определение службы abstract, чтобы избежать дублирования кода (http://symfony.com/doc/current/service_container/parent_services.html).

2/Создайте службу RouteLoader, зарегистрирующую ваши экземпляры Route. Вы можете взглянуть на этот пример: https://github.com/api-platform/core/blob/1.x/Routing/ApiLoader.php

Затем зарегистрируйте этот загрузчик маршрута как службу:

# app/config/services.yml
services:
    app.routing_loader:
        class: AppBundle\Routing\MyLoader
        arguments: ['@some_service', '%some_parameter%]
        tags:
            - { name: routing.loader }

3/Скажите маршрутизатору выполнить этот RouteLoader:

# app/config/routing.yml
app:
    resource: . # Omitted
    type: mytype # Should match the one defined in your loader supports() method

Все сделано!

(Я являюсь членом Symfony Core Team, но также создателем платформы API, поэтому это упрямый ответ.)

Ответ 2

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

Я читал ваш вопрос несколько раз, и я до сих пор не вижу преимуществ такого подхода. Собираетесь ли вы создавать маршрутизаторы по наследству или композиции? Набор правил для определения конкретного (даже если содержит параметры и не полностью "конкретные" ) маршруты должен идти до уровня функции, и даже если это можно решить с помощью хорошего соглашения об именах, я по-прежнему вижу много трудностей.

Просто мнение, конечно.

Ответ 3

Вы можете использовать метод setContainer для проверки контроля доступа пользователя. MySolution:

class AuthBaseController extends Controller{
    /**
    * @var \stdClass
    */
    protected $user = null;

    /**
    * this is a function for any role. For example, edit posts
    * @var int
    */
    protected $functionId=null;

    // this is initilizer function for all controllers. If any controller access to this controller then set $systemAccess to true 
    public function setContainer(ContainerInterface $container = null, $systemAccess= false) {
        parent::setContainer($container);
        if($systemAccess) return;
        $session = $this->get("session");
        if($session->has('YOUR_USER_KEY')){
            $this->user = json_decode($session->get('YOUR_USER_KEY'));
            if(!in_array($this->functionId,$this->user->userFunctions) && !is_null($this->functionId)){
                // if user havn't access to this controller
                throw new AccessDeniedException("You can not access to this page!");
            } 
         }else{
            header("Location:".$this->generateUrl("user_login"));
         }
     }
 }

class TaskManagementController extends AuthBaseController {
     /**
     * @var int
     */
     protected $functionId=24;

     public function indexAction(Request $request){
        //your action codes
      }
}