在 Zend Framework 中呈现页面头部后刷新缓冲区


Flushing the buffer after rendering the head of a page in Zend Framework

我们的应用程序在head标签中有很多CSS和JS,为了提高感知页面加载速度,我希望页面尽快开始获取外部内容,即使在等待服务器提供其他内容时也是如此。基本上,我的目标是让浏览器在等待正文内容交付时开始获取 head 标签中的 CSS 和 JS。

因此,在主布局页面中,我尝试在呈现 head 标签后刷新缓冲区(output_buffering在 php.ini 中关闭):

<?php ob_start(); ?>
<head>
<?php
// A bunch of function calls to render tags for external style sheets and JS
?>
</head>
<?php ob_end_flush(); ?>
<body>
Body content rendered here . . .
</body>

这基本上没有效果。当我查看 Chrome 中的网络活动时,我看到 head 标签和正文标签都是在一个响应中传递的,只有在完成加载后,其他资源才开始按顺序加载,而不是像我预期的那样并行加载。

我尝试了许多其他组合,但没有多少运气。我还读到刷新输出缓冲区后需要调用 flush(),但这给了我奇怪的结果:页面只是显示了一堆奇怪的外来字符(也许它试图显示二进制文件或其他东西?

什至尝试在头部和身体标签之间调用 flush(),但这抛出了一个错误,告诉我我正在尝试在标头已经发送后修改它们,所以这对我也没有做任何有用的事情。

然后,我尝试查看是否存在特定于Zend Framework的解决方案。有人告诉我,我试图做的事情是不可能的,因为 ZF 从内存中的模板渲染整个页面,因此它不能只是在渲染过程中被刷新并发送回浏览器,并且 ZF 基本上忽略了 PHP 的 output_buffering 配置值。

我想我已经用尽了所有可能的组合刷新功能,所以我想知道是否有其他人遇到过类似的问题以及方法是什么。

服务器版本: Apache/2.2.22 (Ubuntu)

(作为旁注,是的,我知道JS应该加载在页面底部,但这不是我将其放在head标签中的要求)

更新 1

得到了我想要的,部分原因是当我尝试以下方法时:

<?php ob_start('ob_gzhandler'); ?>
<head>
<?php
// A bunch of function calls to render tags for external style sheets and JS
?>
</head>
<?php ob_end_flush(); ?>
<body>
Body content rendered here . . .
</body>

我为ob_start添加了回调以ob_gzhandler。这样做会返回一个包含 head 标记的响应,没有正文。这很好,因为一旦 head 标签出现,资源就会开始加载,但 body 标签永远不会到达,页面基本上是空白的。就好像告诉它刷新刷新缓冲区,但脚本在完成执行时再也不会刷新。

我已经在布局脚本的末尾明确添加了对ob_flush(和其他变体)的调用,但它们要么不执行任何操作,要么抱怨没有缓冲区可以刷新(可能是因为我之前调用ob_end)。

从未来开始从事遗留的 ZF1 项目:

这不起作用的原因很简单,因为 ZF 已经在缓冲布局的输出。

https://github.com/zendframework/zf1/blob/136735e776f520b081cd374012852cb88cef9a88/library/Zend/Layout/Controller/Plugin/Layout.php#L108

$obStartLevel = ob_get_level();
try {
    $fullContent = $layout->render();
    $response->setBody($fullContent);
} catch (Exception $e) {
    while (ob_get_level() > $obStartLevel) {
        $fullContent .= ob_get_clean();
    }
    $request->setParam('layoutFullContent', $fullContent);
    $request->setParam('layoutContent', $layout->content);
    $response->setBody(null);
    throw $e;
}

因此,视图呈现中任何其他类型的缓冲区操作都会被刷新到另一个缓冲区。而且您也不能终止更高级别的缓冲区,因为输出不会被setBody

没有适当的解决方案。因此,黑客攻击是:

  • 创建一个新的控制器插件,
  • 重写postDispatch以将头部呈现为单独的布局文件
  • 立即输出磁头,无需输出缓冲器。

https://gist.github.com/darylteo/0fb8cc59dbcc62c90d1b81502408b7c4

这是实验性的,我不确定有什么影响。


不知道ZF2的解决方案是什么,但希望这将在未来帮助一些可怜的灵魂。