Symfony - 更改控制器的实例化和执行方式

Jim*_*mbo 13 php symfony

注意:从版本2.8开始,Symfony提供autowire: true服务配置,从版本3.3开始,Symfony提供alias(而不是autowire_types)将一个具体对象别名为接口,以便自动依赖注入"控制器作为服务".还有一个允许自动装配控制器'动作'方法的包,虽然我已经离开了这个并且更多地关注ADR模式的变体(基本上,这是一个带有接口方法的单个'动作'类)不要在一个类中推动一大堆动作方法,最终导致建筑噩梦).实际上,这是我多年来一直在寻找的东西,现在不再需要"挂钩"一个体面的递归依赖注入器(auryn),因为框架现在处理它应该具有的四年前的东西.我将在这里留下这个答案,以防有人想跟踪我所做的步骤,看看内核是如何工作的,以及这个级别的一些可用选项.


注意:虽然这个问题主要针对Symfony 3,但它也应该与Symfony 2的用户相关,因为内核逻辑似乎没有太大变化.

我想改变控制器在Symfony中的实例化方式.它们实例化的逻辑目前存在于HttpKernel :: handle中,更具体地说,是HttpKernel :: handleRaw.我想call_user_func_array($controller, $arguments)用我自己的注射器代替执行该特定线.

到目前为止我尝试过的选项:

  • HttpKernel::handle用我自己的方法扩展然后通过symfony调用它
http_kernel:
    class: AppBundle\HttpKernel
    arguments: ['@event_dispatcher', '@controller_resolver', '@request_stack']
Run Code Online (Sandbox Code Playgroud)

这样做的缺点是,因为handleRaw是私有的,我不能在没有hacky反射的情况下扩展它,所以我必须复制并粘贴一吨代码.

  • 创建并注册新的控制器解析程序
controller_resolver:
    class: AppBundle\ControllerResolver
    arguments: []
Run Code Online (Sandbox Code Playgroud)

这是我的基本误解,所以我想我会在这里记录下来.解析器的工作是解决地方找到控制器作为调用.它实际上还没有被调用.我非常满意Symfony如何从中获取路由,routes.yml并找出调用控制器作为可调用对象的类和方法.

  • 添加事件侦听器 kernel.request
kernel.request:
    class: MyCustomRequestListener
    tags:
        - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 33 /** Important, we'll get to why in a minute **/ }
Run Code Online (Sandbox Code Playgroud)

看一下Http内核组件文档,我们可以看到它具有以下典型用途:

要向请求添加更多信息,初始化系统的各个部分,或者尽可能返回响应(例如,拒绝访问的安全层).

我想通过创建一个新的监听器,使用我的自定义注入器来创建我的控制器,然后在该监听器中返回响应,将绕过实例化控制器的其余代码.这就是我想要的!但是这有一个重大缺陷:

Symfony Profiler没有显示或任何这些东西,它只是我的回应,就是这样.死.我发现我可以将优先级从31切换到33并让它在我的代码和Symfonys之间切换,我相信这是因为路由器侦听器的优先级.我觉得我在这里走错了路.

不,这允许我更改将被调用的可调用内容call_user_func_array(),而不是实际实例化控制器的方式,这是我的目标.

我记录了我的想法,但我已经出去了.我怎样才能实现以下目标?

  • 更改控制器实例化然后执行的方式,特别call_user_func_array()是在一个血腥的私有方法中(感谢Symfony)
  • 如果我的工作不起作用,则回退到默认控制器实例化
  • 允许其他所有内容按预期工作,例如分析器加载
  • 能够将其与其他用户的扩展捆绑在一起

我为什么要这样做?

控制器可以针对不同的情况使用许多不同的方法,并且每个方法应该能够为其单独需要的内容输入提示,而不是让构造函数接受所有的东西,其中一些甚至可能根据正在执行的控制器方法而不使用.控制器并不真正遵守单一责任原则,而且它们是"对象边缘案例".但他们就是这样.

我想用我自己的递归自动装配注入器替换控制器的创建方式,以及它们的执行方式,再次通过我的注入器进行递归内省,因为默认的Symfony软件包似乎没有这个功能.即使是Symfony 2.8+中最新的"autowire"服务选项.

nik*_*206 0

为什么不从自定义返回自己的可调用对象,ControllerResolverInterface该可调用对象会Controller以您想要的方式实例化并调用它?

它基本上是一个装饰器。

您可以Symfony\Component\HttpKernel\Controller\ControllerResolver使用自己的instantiateController()方法实现进行扩展,也可以ControllerResolverInterface从头开始实现。

更新:

当 Symfony 调用 时call_user_func_array($controller, $arguments);handleRaw()$controller变量就是您从自定义返回的变量ControllerResolver。这意味着您可以从解析器返回任何可调用对象(可以是[$this, "callController"]fe),并且在该可调用对象内您可以Controller使用 Auryn 创建一个新对象并调用它。

更新2:

如果您仍然对此感到困惑,我将添加一个示例,因为您可能会错过我在这里的意思。

use Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver;

class AutowiringControllerResolver extends ControllerResolver
{
    // ... constructor stuff; assume $injector is a part of this class

    protected function createController($controller)
    {
        $controller = parent::createController($controller);

        return function (...$arguments) use ($controller) {
            // you can do with resolved $arguments whatever you want
            // or you can override getArguments() method and return
            // empty array to discard getArguments() functionality completely

            return $this->injector->execute($controller);
        };
    }

    protected function instantiateController($classname)
    {
        return $this->injector->make($classname);
    }
}
Run Code Online (Sandbox Code Playgroud)