如何从 Silex 中的控制器发送多个渲染?
How to send multiple rendering from a controller in Silex?
为了修复用户等待的痛苦 由于一些(已经优化)DB 计算:大约 3 到 10 秒.
我们需要在漫长的计算过程中制作一个等待页面,例如每个航班比较器所做的。
我们的架构基于 Silex 1.3。
我们要实现的是:
- 通过路由URL匹配触发控制器动作并开始计算
- 在不修改请求的情况下进行首次渲染:twig 等待页面
- 在计算结束时用所有数据渲染最终的树枝
由计算驱动
我用绑定到路由的 $app->before
属性对其进行了测试,returning/rendering 停止了计算...
那么那个基于计算的双重渲染怎么做呢?
不适合我们的目的的解决方案:
我实施的第一个解决方法是 JavaScript show/hide 微调器元素。
但这不是一个合适的解决方案,因为我们确实需要从服务器获取临时渲染。
编辑
第一个渲染工作正常,但我无法通过调用正在执行渲染的控制器操作(定义为控制器即服务)来进行第二个渲染,我得到了这个 EXCEPTION:
RuntimeException: Accessed request service outside of request scope. Try moving that call to a before handler or controller.
这是我的控制器定义:
$app['index.controller'] = $app->share(function() use ($app) {
return new IndexController($app);
});
这是我的路由定义:
$app->get('/vue-ensemble/{city}', function (Request $request, Application $app)
{
$content = function() use ($app) {
$wait = $app->render('index_test.twig', array());
$wait->send();
flush();
// Long process
$process = $app['index.controller']->overviewAction();
$process->send();
flush();
};
return $app->stream($content);
});
这是我的控制器操作:
protected $app;
public function __construct(Application $app)
{
$this->app = $app;
}
public function overviewAction(){
/* DO LONG PROCESS */
return $this->app->render('overview.twig', array('some elements'=>'some values'));
}
编辑 '
不幸的是,我仍然遇到同样的问题,这是堆栈跟踪:
fatal error: Uncaught exception 'RuntimeException' with message 'Accessed request service outside of request scope. Try moving that call to a before handler or controller.' in C:\inforisq\application\vendor\silex\silex\src\Silex\Application.php on line 150
( ! ) RuntimeException: Accessed request service outside of request scope. Try moving that call to a before handler or controller. in C:\inforisq\application\vendor\silex\silex\src\Silex\Application.php on line 150
Call Stack
# Time Memory Function Location
1 0.0010 240848 {main}( ) ...\index.php:0
2 0.5250 4506656 Silex\Application->run( ) ...\index.php:14
3 0.7040 13098664 Symfony\Component\HttpFoundation\Response->send( ) ...\Application.php:564
4 0.7040 13101248 Symfony\Component\HttpFoundation\StreamedResponse->sendContent( ) ...\Response.php:372
5 0.7040 13101296 call_user_func:{C:\inforisq\application\vendor\symfony\http-foundation\StreamedResponse.php:90} ( ) ...\StreamedResponse.php:90
6 0.7040 13101384 {closure:C:\inforisq\application\app\config\routing.php:20-27}( ) ...\StreamedResponse.php:90
7 0.7040 13118168 Inforisq\Controller\IndexController->overviewAction( ) ...\routing.php:25
8 0.7040 13118328 Lib\InforisqApplication->place_analyzeURL( ) ...\IndexController.php:64
9 0.7060 13306184 Indicator\Repository\PlaceRepository->analyzeURLPlace( ) ...\PlaceTrait.php:23
10 0.7060 13306304 Lib\InforisqApplication->request( ) ...\PlaceRepository.php:423
11 0.7060 13306384 Pimple->offsetGet( ) ...\PlaceRepository.php:26
12 0.7060 13306464 Silex\Application->Silex\{closure}( ) ...\Pimple.php:83
您需要使用 Symfony\Component\HttpFoundation\StreamedResponse
见https://symfony.com/doc/current/components/http_foundation/introduction.html#streaming-a-response
编辑
当您 return 来自控制器的响应时,内核调用 $response->send()
,但在内部 Response::send()
调用 Response::sendHeaders()
然后 Response::sendContent()
.
所以 sendHeaders()
在这种情况下将由内核在流式响应上发送一次,然后如果您需要其他 Response
objects 在您的回调中为了方便,您必须调用只有 sendContent()
.
如果您需要自定义 http 响应代码或 headers,您可以将它们作为参数传递给方法 Application::stream($callback, $statusCode, array $headers)
。
在编辑我的答案之前,我使用了 flush()
,就像 symfony 文档中的示例一样,但是您可能需要一些缓存才能处理回调中的第二个控制器,因此首先使用 ob_start()
和ob_flush()
用于 "waiting" 响应。
use \Silex\Application;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Response;
$app->get('/vue-ensemble/{city}', function (Request $request, Application $app, $city)
{
/** @var \Acme\Controller\IndexController $indexController */
$indexController = $app['index.controller'];
/** @var Response $wait */
$wait = $app->render('wait.html.twig', array('city' => $city);
// Callback
$content = function () use ($wait, $indexController) {
ob_start();
$wait->sendContent();
ob_flush();
$indexController->overviewAction($city)->sendContent();
flush();
}
return $app->stream($content);
// Will send a \Symfony\Component\HttpFoundation\StreamedResponse
// equivalent to :
// $streamedResponse = new StreamedResponse();
// $streamedResponse->setCallback($content);
//
// return streamedResponse;
});
编辑 2:
你应该只在服务的构造函数中传递你需要的服务,而不是每次都传递整个容器:
$app['index.controller'] = $app->share(function() use ($app) {
return new IndexController($app['twig'], $app['some_helper']);
});
$app['some_helper'] = $app->protect(function($arg1, arg2) use ($app) {
$helper = new \Acme\Helper($app['some_dependency'];
return $helper->help(arg1, arg2);
});
然后:
class IndexController
{
protected $twig;
protected $helper;
public function __construct(\Twig_Engine $twig, \Acme\Helper $helper)
{
$this->twig = $twig;
$this->helper = $helper;
}
public function overview($city)
{
$some_arg = ...
$viewArg = $this->helper($some_arg, $city);
return $this->twig->renderResponse('overview.twig', array('some elements' => $viewArg));
}
}
但是如果 indexController
只是 returned $this->twig->render(...)
就更好了,但它应该是 echo $indexController->overview($city)
为了修复用户等待的痛苦 由于一些(已经优化)DB 计算:大约 3 到 10 秒.
我们需要在漫长的计算过程中制作一个等待页面,例如每个航班比较器所做的。
我们的架构基于 Silex 1.3。
我们要实现的是:
- 通过路由URL匹配触发控制器动作并开始计算
- 在不修改请求的情况下进行首次渲染:twig 等待页面
- 在计算结束时用所有数据渲染最终的树枝 由计算驱动
我用绑定到路由的 $app->before
属性对其进行了测试,returning/rendering 停止了计算...
那么那个基于计算的双重渲染怎么做呢?
不适合我们的目的的解决方案:
我实施的第一个解决方法是 JavaScript show/hide 微调器元素。 但这不是一个合适的解决方案,因为我们确实需要从服务器获取临时渲染。
编辑
第一个渲染工作正常,但我无法通过调用正在执行渲染的控制器操作(定义为控制器即服务)来进行第二个渲染,我得到了这个 EXCEPTION:
RuntimeException: Accessed request service outside of request scope. Try moving that call to a before handler or controller.
这是我的控制器定义:
$app['index.controller'] = $app->share(function() use ($app) {
return new IndexController($app);
});
这是我的路由定义:
$app->get('/vue-ensemble/{city}', function (Request $request, Application $app)
{
$content = function() use ($app) {
$wait = $app->render('index_test.twig', array());
$wait->send();
flush();
// Long process
$process = $app['index.controller']->overviewAction();
$process->send();
flush();
};
return $app->stream($content);
});
这是我的控制器操作:
protected $app;
public function __construct(Application $app)
{
$this->app = $app;
}
public function overviewAction(){
/* DO LONG PROCESS */
return $this->app->render('overview.twig', array('some elements'=>'some values'));
}
编辑 '
不幸的是,我仍然遇到同样的问题,这是堆栈跟踪:
fatal error: Uncaught exception 'RuntimeException' with message 'Accessed request service outside of request scope. Try moving that call to a before handler or controller.' in C:\inforisq\application\vendor\silex\silex\src\Silex\Application.php on line 150
( ! ) RuntimeException: Accessed request service outside of request scope. Try moving that call to a before handler or controller. in C:\inforisq\application\vendor\silex\silex\src\Silex\Application.php on line 150
Call Stack
# Time Memory Function Location
1 0.0010 240848 {main}( ) ...\index.php:0
2 0.5250 4506656 Silex\Application->run( ) ...\index.php:14
3 0.7040 13098664 Symfony\Component\HttpFoundation\Response->send( ) ...\Application.php:564
4 0.7040 13101248 Symfony\Component\HttpFoundation\StreamedResponse->sendContent( ) ...\Response.php:372
5 0.7040 13101296 call_user_func:{C:\inforisq\application\vendor\symfony\http-foundation\StreamedResponse.php:90} ( ) ...\StreamedResponse.php:90
6 0.7040 13101384 {closure:C:\inforisq\application\app\config\routing.php:20-27}( ) ...\StreamedResponse.php:90
7 0.7040 13118168 Inforisq\Controller\IndexController->overviewAction( ) ...\routing.php:25
8 0.7040 13118328 Lib\InforisqApplication->place_analyzeURL( ) ...\IndexController.php:64
9 0.7060 13306184 Indicator\Repository\PlaceRepository->analyzeURLPlace( ) ...\PlaceTrait.php:23
10 0.7060 13306304 Lib\InforisqApplication->request( ) ...\PlaceRepository.php:423
11 0.7060 13306384 Pimple->offsetGet( ) ...\PlaceRepository.php:26
12 0.7060 13306464 Silex\Application->Silex\{closure}( ) ...\Pimple.php:83
您需要使用 Symfony\Component\HttpFoundation\StreamedResponse
见https://symfony.com/doc/current/components/http_foundation/introduction.html#streaming-a-response
编辑
当您 return 来自控制器的响应时,内核调用 $response->send()
,但在内部 Response::send()
调用 Response::sendHeaders()
然后 Response::sendContent()
.
所以 sendHeaders()
在这种情况下将由内核在流式响应上发送一次,然后如果您需要其他 Response
objects 在您的回调中为了方便,您必须调用只有 sendContent()
.
如果您需要自定义 http 响应代码或 headers,您可以将它们作为参数传递给方法 Application::stream($callback, $statusCode, array $headers)
。
在编辑我的答案之前,我使用了 flush()
,就像 symfony 文档中的示例一样,但是您可能需要一些缓存才能处理回调中的第二个控制器,因此首先使用 ob_start()
和ob_flush()
用于 "waiting" 响应。
use \Silex\Application;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Response;
$app->get('/vue-ensemble/{city}', function (Request $request, Application $app, $city)
{
/** @var \Acme\Controller\IndexController $indexController */
$indexController = $app['index.controller'];
/** @var Response $wait */
$wait = $app->render('wait.html.twig', array('city' => $city);
// Callback
$content = function () use ($wait, $indexController) {
ob_start();
$wait->sendContent();
ob_flush();
$indexController->overviewAction($city)->sendContent();
flush();
}
return $app->stream($content);
// Will send a \Symfony\Component\HttpFoundation\StreamedResponse
// equivalent to :
// $streamedResponse = new StreamedResponse();
// $streamedResponse->setCallback($content);
//
// return streamedResponse;
});
编辑 2:
你应该只在服务的构造函数中传递你需要的服务,而不是每次都传递整个容器:
$app['index.controller'] = $app->share(function() use ($app) {
return new IndexController($app['twig'], $app['some_helper']);
});
$app['some_helper'] = $app->protect(function($arg1, arg2) use ($app) {
$helper = new \Acme\Helper($app['some_dependency'];
return $helper->help(arg1, arg2);
});
然后:
class IndexController
{
protected $twig;
protected $helper;
public function __construct(\Twig_Engine $twig, \Acme\Helper $helper)
{
$this->twig = $twig;
$this->helper = $helper;
}
public function overview($city)
{
$some_arg = ...
$viewArg = $this->helper($some_arg, $city);
return $this->twig->renderResponse('overview.twig', array('some elements' => $viewArg));
}
}
但是如果 indexController
只是 returned $this->twig->render(...)
就更好了,但它应该是 echo $indexController->overview($city)