Symfony在发送响应后运行代码

Geo*_*iuc 9 php httpresponse symfony

我看了看这个问题.我正在寻找一种方法来做那个问题的OP也想要的,那就是在发送http响应之后继续处理php,但是在Symfony2中.

我实现了一个在每次内核终止后触发的事件.到目前为止一切都那么好,但我想要的是它在CERTAIN终止后,在特定的控制器动作中触发,例如在发送表单之后,而不是每次请求时.这是因为我想在某些时候做一些繁重的任务,并且不希望最终用户等待页面加载.

知道我该怎么办?

<?php


namespace MedAppBundle\Event;

use JMS\DiExtraBundle\Annotation\InjectParams;
use JMS\DiExtraBundle\Annotation\Service;
use JMS\DiExtraBundle\Annotation\Tag;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use JMS\DiExtraBundle\Annotation\Inject;
/**
 * Class MedicListener
 * @package MedAppBundle\EventListener
 * @Service("medapp_test.listener")
 * @Tag(name="kernel.event_subscriber")
 */
class TestListener implements EventSubscriberInterface
{
    private $container;

    private $logger;

    /**
     * Constructor.
     *
     * @param ContainerInterface $container A ContainerInterface instance
     * @param LoggerInterface $logger A LoggerInterface instance
     * @InjectParams({
     *     "container" = @Inject("service_container"),
     *     "logger" = @Inject("logger")
     * })
     */
    public function __construct(ContainerInterface $container, LoggerInterface $logger = null)
    {
        $this->container = $container;
        $this->logger = $logger;
    }

    public function onTerminate()
    {
      $this->logger->notice('fired');
    }

    public static function getSubscribedEvents()
    {
        $listeners = array(KernelEvents::TERMINATE => 'onTerminate');

        if (class_exists('Symfony\Component\Console\ConsoleEvents')) {
            $listeners[ConsoleEvents::TERMINATE] = 'onTerminate';
        }

        return $listeners;
    }
}
Run Code Online (Sandbox Code Playgroud)

到目前为止,我已经将事件订阅到kernel.terminate一个,但显然这会在每次请求时触发它.我使它类似于Swiftmailer的EmailSenderListener

内核必须每次都听这个事件,即使它没有被触发,这有点奇怪.我宁愿只在需要时解雇它,但不知道该怎么做.

Tob*_* Xy 8

在onTerminate回调你得到的实例PostResponseEvent作为第一个参数.您可以从该对象获取请求以及响应.然后,您应该能够决定是否要运行实际的终止代码.

您还可以将自定义数据存储在请求的属性包中.请参阅此链接:Symfony和HTTP Fundamentals

Request类还有一个public属性,它包含与应用程序内部工作方式相关的特殊数据.对于Symfony框架,属性保存匹配路由返回的值,如_controller,id(如果您有{id}通配符),甚至是匹配路由的名称(_route).attributes属性完全存在于您可以准备和存储有关请求的特定于上下文的信息的位置.

您的代码可能如下所示:

// ...

class TestListener implements EventSubscriberInterface
{
    // ...

    public function onTerminate(PostResponseEvent $event)
    {
        $request = $event->getRequest();
        if ($request->attributes->get('_route') == 'some_route_name') {
            // do stuff
        }
    }

    // ...
}
Run Code Online (Sandbox Code Playgroud)

编辑:

kernel.terminate事件旨在在发送响应后运行.但是symfony文档说的如下(取自这里):

在内部,HttpKernel使用fastcgi_finish_request PHP函数.这意味着目前只有PHP FPM服务器API能够向客户端发送响应,而服务器的PHP进程仍然执行某些任务.对于所有其他服务器API,仍然会执行kernel.terminate的侦听器,但在完成所有响应之前,响应不会发送到客户端.

编辑2:

要使用此处的解决方案,您可以直接编辑web/app.php文件以将其添加到那里(但这是某种"黑客核心"imo,即使它比以下更容易使用).或者你可以这样做:

  1. 向具有高优先级的kernel.request事件添加侦听器并启动输出缓冲(ob_start).
  2. 向kernel.response添加一个监听器,并将标头值添加到响应中.
  3. 将另一个具有最高优先级的侦听器添加到kernel.terminate并执行刷新(ob_flush,flush).
  4. 在kernel.terminate的优先级较低的单独侦听器中运行代码

我没试过,但它确实应该有效.