Symfony: Фабрика контроллеров

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

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

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

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

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

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

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

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

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

  3. есть ли другой вариант?

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

3 ответов


первая версия платформы API использовал аналогичную технику.

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

это, как платформа API, Соната и Easy Admin работают, например.

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

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

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

подведем итоги:

1 / Регистрация определения сервиса для вашего контроллера, используя ваш завод, чтобы построить его, как показано ниже:

# 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's supports() method

все готово!

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


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

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

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


вы можете использовать метод 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
      }
}