Symfony:控制器工厂

Symfony: Factory of controllers

我正在制作一个自定义用户包,允许定义多个用户类型,具有他们自己的存储库、管理器、提供者等。因此,我决定不创建有限的控制器集,而是创建一个控制器工厂,它将根据定义的用户类型和配置生成控制器。但这提出了一个重要的问题 - 这些工厂应该在哪里以及如何运作?

现在请注意,在工厂中创建控制器是不够的,我们还必须在某个地方为它设置所有路由。

问题是 - 最好的架构是什么?

在选择放置我的代码的图层时,我考虑的因素包括:

  1. 在 Extension 的 load 方法中加载工厂定义,并在那里创建所有控制器。问题:路由器在那里不可用,因为它发生在容器构建之前,所以我无法在同一个地方创建路由。

  2. Sooo...也许在编译器中通过?但是编译器通道无法访问配置...我的意思是...事实上它有,如果我只是加载配置并手动处理它,但我仍然不确定这是否是一个好地方,但我我现在倾向于这个解决方案。

创建路由时:

  1. 我应该将路由创建逻辑放在控制器工厂中吗?但是我正在创建控制器作为服务,工厂无法访问创建的控制器的 serviceId,并且创建路由需要 serviceId,所以不行。

  2. 在控制器本身?我的意思是,这就是注释路由的工作方式,所以它可能是可行的。 Controller 必须使用方法 getRoutes 实现类似我自己的 ControllerInterface 的东西,而外部 service/compiler pass 需要先创建一个控制器作为服务,然后从上述方法中获取路由controller,修改它们,所以他们会参考这个控制器的 serviceId 并将它们添加到路由器......不管这看起来有多混乱。

  3. 还有其他选择吗?

关于这个特定模式的信息相当缺乏-控制器工厂:)。

您可以使用 setContainer 方法检查用户访问控制。 我的解决方案:

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

要操作那个工厂,首先你需要定义一些规则来在编译过程中使用自定义路由加载器创建路由,我想你还需要自定义路由匹配和解析过程以便检查接收到的路由,然后是定义路由模式或值与工厂创建的具体路由器之间关系的规则,最后将请求传递给具体路由器中的函数。

你的问题我看了好几遍,还是看不出这种做法有什么好处。您打算通过继承还是组合来创建路由器?定义具体(即使包含参数并且不完全"concrete")路由的规则集需要一直到功能级别,即使这可以通过一个好的命名约定来解决,我仍然看到很多困难。

当然只是个人意见。

The first version of API Platform 使用了类似的技术。

第一步是注册路由。路由将 URL 模式映射到 _controller 路由属性下定义的控制器。这就是 Routing 组件和 HttpKernel 组件如何链接在一起的(这两个组件之间没有强耦合)。 可以通过创建 RouteLoader 来注册路由:http://symfony.com/doc/current/routing/custom_route_loader.html

例如,API Platform、Sonata 和 Easy Admin 就是这样工作的。

在运行时,将执行 _controller 属性下指定的可调用对象。它将在参数中接收 HTTP 请求,并且应该 return 一个 HTTP 响应。如果需要,它可以访问其他服务(甚至容器)。

控制器可以是任何可调用的(方法、函数、可调用的 class...),但它也可以是服务,这要归功于以下语法 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 class 中以编程方式创建控制器定义。您还可以使用 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 平台创建者,所以这是一个自以为是的答案。)