Symfony: Фабрика контроллеров
Я делаю пользовательский пакет, позволяющий определять несколько типов пользователей со своими собственными репозиториями, менеджерами, поставщиками и т. д. Поэтому я решил вместо создания ограниченного набора контроллеров создать фабрики контроллеров, которые будут производить контроллеры на основе определенных типов пользователей и конфигурации. Но в связи с этим возникает важный вопрос - где и как должны работать эти заводы?
Теперь имейте в виду, что недостаточно создать контроллер в фабрика, мы также должны настроить все маршруты для нее, где-то.
вопрос в том, что было бы лучшей архитектурой для этого?
когда дело доходит до выбора слоя, где я буду размещать свой код, я рассматривал, среди прочего:
загрузка определений фабрик в расширение
load
метод и создание всех контроллеров там. Проблема: маршрутизатор недоступен там, потому что это происходит до создания контейнера, поэтому я не мог создавать маршруты в одном месте.ооочень... может быть, в компиляторе? Но compiler pass не имеет доступа к конфигурации... Я имею в виду... на самом деле это так, если я просто загружу конфигурацию и обработаю ее вручную, но я все еще не уверен, что это хорошее место, но я склоняюсь к этому решению прямо сейчас.
когда дело доходит до создания маршрутов:
должен ли я разместить логику создания маршрутов в фабрика контроллеров? Но я создаю контроллеры, поскольку службы и фабрика не имеют доступа к serviceId созданного контроллера, а serviceId необходим для создания маршрута, поэтому нет.
В самом контроллере? Я имею в виду, так работают маршруты аннотаций, так что это может быть жизнеспособным. Контроллер должен был бы реализовать что-то вроде моего
ControllerInterface
методомgetRoutes
, и для прохождения внешней службы / компилятора потребуется создать контроллер в качестве службы сначала, а затем получите маршруты от указанного контроллера, измените их, чтобы они ссылались на serviceId этого контроллера и добавляли их к маршрутизатору... независимо от того, как грязно это выглядит.есть ли другой вариант?
существует значительный недостаток информации относительно этого конкретного шаблона-фабрики контроллеров :).
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
}
}