我正在制作一个自定义用户包,允许使用自己的存储库,管理器,提供商等定义多个用户类型。因此,我决定创建一个控制器工厂,而不是创建有限的控制器组,将根据定义的用户类型和配置生成控制器。但这引出了一个重要的问题 - 这些工厂应该在哪里运营?以及这些工厂应该如何运作?
现在,请注意,在工厂中创建控制器是不够的,我们还必须在某处设置所有路径。
问题是 - 什么是最好的架构呢?
当谈到选择我将放置代码的图层时,我正在考虑,其中包括:
在Extension的load
方法中加载工厂定义,并在那里创建所有控制器。问题:路由器在那里不可用,因为它发生在容器构建之前,所以我无法在同一个地方创建路由。
Sooo ......可能在编译器传递中?但编译器传递无法访问配置...我的意思是......实际上它已经,如果我只是加载配置并手动处理它,但我仍然不确定这是否是一个好地方,但我我现在正倾向于这个解决方案。
创建路线时:
我应该在控制器工厂中放置路由创建逻辑吗?但是我正在创建控制器作为服务,工厂无法访问创建的控制器的serviceId,并且创建路由需要serviceId,所以nope。
在控制器本身?我的意思是,这就是注释路由的工作方式,因此它可能是可行的。控制器必须使用方法ControllerInterface
实现类似我自己的getRoutes
,并且外部服务/编译器传递需要首先创建控制器作为服务,然后从所述控制器获取路由,修改它们,所以他们会引用这个控制器的serviceId并将它们添加到路由器......无论这看起来多么混乱。
还有其他选择吗?
关于这种特殊模式的信息非常缺乏 - 控制器工厂:)。
答案 0 :(得分:5)
The first version of API Platform正在使用类似的技术。
第一步是注册路线。路径使用在_controller
路由属性下定义的控制器映射URL模式。路由组件和HttpKernel组件是如何链接在一起的(这两个组件之间没有强耦合)。
可以通过创建RouteLoader
:http://symfony.com/doc/current/routing/custom_route_loader.html
这就是API平台,Sonata和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。 Factory方法可以接收其他服务或参数(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核心团队成员,但也是API平台创建者,因此这是一个自以为是的答案。)
答案 1 :(得分:2)
要操作那些工厂,首先需要在编译过程中使用自定义路由加载器定义一些规则来创建路由,我想你还应该自定义路由匹配和解析过程以检查路由收到,然后定义路由模式或值与工厂创建的具体路由器之间的关系的规则,最后将请求传递给具体路由器内的功能。
我已多次阅读您的问题,但我仍然没有看到这种方法的优点。你打算通过继承或组合来创建路由器吗?定义具体的规则集(即使包含参数并且不完全"具体")路由需要一直到功能级别,甚至可以通过良好的命名约定来解决,我仍然看到很多困难。
当然是一种意见。
答案 2 :(得分:0)
您可以使用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
}
}