Php 发送响应后的Symfony运行代码
我看了一眼。我正在寻找一种方法来做这个问题的OP想要做的事情,也就是说,但是在Symfony2中 我实现了一个在每次内核终止后触发的事件。到目前为止还不错,但我想让它在特定的终止之后,在特定的控制器操作中,例如在发送表单之后,而不是在每次请求时都触发。这是因为我想在某些时候做一些繁重的任务,不希望最终用户等待页面加载 知道我该怎么做吗Php 发送响应后的Symfony运行代码,php,symfony,httpresponse,Php,Symfony,Httpresponse,我看了一眼。我正在寻找一种方法来做这个问题的OP想要做的事情,也就是说,但是在Symfony2中 我实现了一个在每次内核终止后触发的事件。到目前为止还不错,但我想让它在特定的终止之后,在特定的控制器操作中,例如在发送表单之后,而不是在每次请求时都触发。这是因为我想在某些时候做一些繁重的任务,不希望最终用户等待页面加载 知道我该怎么做吗 <?php namespace MedAppBundle\Event; use JMS\DiExtraBundle\Annotation\Inject
<?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;
}
}
在onTerminate回调中,您将获得的实例作为第一个参数。您可以从该对象获得请求和响应。
然后,您应该能够决定是否要运行实际的终止代码
您还可以在请求的属性包中存储自定义数据。请参阅此链接:
请求类还有一个公共属性,它保存与应用程序内部工作方式相关的特殊数据。对于Symfony框架,属性保存由匹配路由返回的值,如_controller、id(如果有{id}通配符),甚至还有匹配路由的名称(_route)。attributes属性的存在完全是为了让您可以准备和存储有关请求的特定于上下文的信息
您的代码可能如下所示:
/。。。
类TestListener实现EventSubscriberInterface
{
// ...
终端上的公共函数(PostResponseEvent$事件)
{
$request=$event->getRequest();
如果($request->attributes->get('u route')=='some'u route\u name'){
//做事
}
}
// ...
}
编辑:
kernel.terminate事件设计为在发送响应后运行。但是symfony文档中说了以下内容(摘自):
在内部,HttpKernel使用fastcgi_finish_request PHP函数。这意味着目前只有PHP FPM服务器API能够向客户端发送响应,而服务器的PHP进程仍在执行某些任务。对于所有其他服务器API,kernel.terminate的侦听器仍在执行,但在它们全部完成之前,不会将响应发送到客户端
编辑2:
要使用来自的解决方案,您可以直接编辑web/app.php文件将其添加到那里(但这是imo的某种“黑客核心”,尽管它比下面的更容易使用)。或者你可以这样做:
将侦听器添加到具有高优先级的kernel.request事件并启动输出缓冲(ob_start)
将侦听器添加到kernel.response并将头值添加到响应
将另一个具有最高优先级的侦听器添加到kernel.terminate并进行刷新(ob_flush,flush)
在一个单独的侦听器中运行代码,优先级低于kernel.terminate
我没有尝试过,但它实际上应该可以工作。我使用这些答案编写了一个具有以下功能的响应类:
此实现将在Apache上工作,而不仅仅是在PHP FPM上。然而,为了实现这一点,我们必须防止Apache使用gzip(通过使用无效的内容编码),因此有一个定制的响应类来精确地指定何时具有早期响应比压缩更重要
use Symfony\Component\HttpFoundation\Response;
class EarlyResponse extends Response
{
// Functionality adapted from this answer: https://stackoverflow.com/a/7120170/1153227
protected $callback = null;
/**
* Constructor.
*
* @param mixed $content The response content, see setContent()
* @param int $status The response status code
* @param array $headers An array of response headers
*
* @throws \InvalidArgumentException When the HTTP status code is not valid
*/
public function __construct($content = '', $status = 200, $headers = array(), $callback = null)
{
if (null !== $callback) {
$this->setTerminateCallback($callback);
}
parent::__construct($content, $status, $headers);
}
/**
* Sets the PHP callback associated with this Response.
* It will be called after the terminate events fire and thus after we've sent our response and closed the connection
*
* @param callable $callback A valid PHP callback
*
* @throws \LogicException
*/
public function setTerminateCallback($callback)
{
//Copied From Symfony\Component\HttpFoundation\StreamedResponse
if (!is_callable($callback)) {
throw new \LogicException('The Response callback must be a valid PHP callable.');
}
$this->callback = $callback;
}
/**
* @return Current_Class_Name
*/
public function send() {
if (function_exists('fastcgi_finish_request') || 'cli' === PHP_SAPI) { // we don't need the hack when using fast CGI
return parent::send();
}
ignore_user_abort(true);//prevent apache killing the process
if (!ob_get_level()) { // Check if an ob buffer exists already.
ob_start();//start the output buffer
}
$this->sendContent(); //Send the content to the buffer
static::closeOutputBuffers(1, true); //flush all but the last ob buffer level
$this->headers->set('Content-Length', ob_get_length()); // Set the content length using the last ob buffer level
$this->headers->set('Connection', 'close'); // Close the Connection
$this->headers->set('Content-Encoding', 'none');// This invalid header value will make Apache not delay sending the response while it is
// See: https://serverfault.com/questions/844526/apache-2-4-7-ignores-response-header-content-encoding-identity-instead-respect
$this->sendHeaders(); //Now that we have the headers, we can send them (which will avoid the ob buffers)
static::closeOutputBuffers(0, true); //flush the last ob buffer level
flush(); // After we flush the OB buffer to the normal buffer, we still need to send the normal buffer to output
session_write_close();//close session file on server side to avoid blocking other requests
return $this;
}
/**
* @return Current_Class_Name
*/
public function callTerminateCallback() {
if ($this->callback) {
call_user_func($this->callback);
}
return $this;
}
}
您还需要在AppKernel.php中添加一个方法来实现这一点(不要忘记为EarlyResponse类添加一个use语句)
为了解决我的一些用例的这个问题,我只需创建symfony命令来执行繁重的任务,并通过exec()调用它们,使它们在单独的进程中运行。我在哪里执行繁重的任务?我需要启动任务,并在发送响应后运行它。响应不能等待任务完成。我必须像这样“做事”吗?或者默认情况下响应是在onTerminate()函数中发生的任何事件之后发送的?terminate事件设计为在发送响应之后运行。因此,当调用“onTerminate”时,响应应该已经发送给用户了。显然,在实践中,情况并非总是如此。检查我编辑的答案。嗯,在这种情况下,我可以做什么来发送响应并继续执行?似乎出于设计,Symfony不允许我在其他答案中使用其他方法。我也不能真正测试它,因为繁重的任务是用Symfony代码编写的。我认为可以使用另一个答案。检查我第二次编辑的答案。这对我来说非常有效,谢谢!请注意,如果决定扩展JsonResponse
或streamdresponse
,则必须使用与$response
不同的属性名称。
public function terminate(Request $request, Response $response)
{
ob_start();
//Run this stuff before the terminate events
if ($response instanceof EarlyResponse) {
$response->callTerminateCallback();
}
//Trigger the terminate events
parent::terminate($request, $response);
//Optionally, we can output the beffer that will get cleaned to a file before discarding its contents
//file_put_contents('/tmp/process.log', ob_get_contents());
ob_end_clean();
}