如何从 Silex 中的控制器发送多个渲染?

How to send multiple rendering from a controller in Silex?

为了修复用户等待的痛苦 由于一些(已经优化)DB 计算:大约 3 到 10 秒.

我们需要在漫长的计算过程中制作一个等待页面,例如每个航班比较器所做的。

我们的架构基于 Silex 1.3

我们要实现的是:

  1. 通过路由URL匹配触发控制器动作并开始计算
  2. 在不修改请求的情况下进行首次渲染:twig 等待页面
  3. 在计算结束时用所有数据渲染最终的树枝 由计算驱动

我用绑定到路由的 $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)