4

Our application has a lot of CSS and JS in the head tag and in an effort to improve perceived page load speed, I wanted the page to start fetching the external content as soon as possible, even while waiting for the other content to be served up by the server. Basically my goal was to get the browser to start fetching the CSS and JS in the head tag while it waited for the body content to be delivered.

So in the main layout page, I tried flushing the buffer after the head tag was rendered (output_buffering was turned off in 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>

This had basically no effect. When I looked at the network activity in Chrome, I saw that the head tag and the body tag were all delivered in one response and only after that finished loading, did the other resources start loading sequentially, instead of in parallel, as I expected.

I tried a number of other combinations, without much luck. I also read that flushing the output buffer needs to be followed with a call to flush() but that gave me weird results: the page just displayed a bunch of strange foreign characters (maybe it was trying to display a binary file or something?).

I even tried using nothing but a call to flush() between the head and body tags, but that threw an error telling me that I'm trying to modify headers after they were already sent, so that didn't do anything useful for me, either.

I then tried to see if there was a solution that existed specific to Zend Framework. Someone told me what I'm trying to do is not possible because ZF renders the entire page from a template in memory and so it can't just be flushed out in the middle of rendering and sent back to the browser, and that ZF basically ignores PHP's output_buffering config value.

I think I've exhausted all the possible combinations flushing functions, so I was wondering if anyone else has had a similar problem and what the approach was.

Server version: Apache/2.2.22 (Ubuntu)

(As a side note, yes I know that JS should be loaded at the bottom of the page, but that was not my call to put it in the head tag)

UPDATE 1

I'm getting what I want, partially when I try the following:

<?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>

I added a callback for ob_start to ob_gzhandler. Doing this returns a response that contains the head tag, with no body. This is good, because as soon as the head tag comes the resources start loading, but the body tag never arrives and the page is basically blank. It is as if telling it to flush flushes buffer, but then the script never flushes again when it finishes executing.

I've explicitly added a call to ob_flush (and other variants) to the end of the layout script, but they either don't do anything or complain there is no buffer to flush (probably because I called ob_end before).

The Unknown Dev
  • 3,039
  • 4
  • 27
  • 39

1 Answers1

1

From the future working on a legacy ZF1 project:

The reason why this does not work is simply that ZF is already buffering the output of the layout.

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;
}

So any other kind of buffer manipulation within the view rendering will simply just get flushed to the other buffer. And you can't terminate the higher level buffer either because the output will not be captured by the setBody

There is no proper solution. The hackaround is thus:

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

This is experimental and I'm not sure what the impacts are.


Don't know what the solution is for ZF2, but hopefully this will help some poor soul in the future.

Daryl Teo
  • 5,394
  • 1
  • 31
  • 37
  • After some testing it's clear that this has NO general benefit whatsoever. These are only fired after all controller work is done (hence, post dispatch). So the next potential solution is to actually use renderScript() to render the head in your action before any meaningful work is done. Just be sure that this is done only after all headers have been sent. – Daryl Teo Nov 18 '19 at 02:04