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
}
}