Na navigaci | Klávesové zkratky

Everything About Output Buffering in PHP

And what you won't read in the documentation, including a security patch and advice on speeding up server response without slowing it down.

Output buffering allows the output of a PHP script (primarily from the echo function) to be stored in memory (i.e., a buffer) instead of being sent immediately to the browser or terminal. This is useful for various purposes.

Preventing Output to the Screen:

ob_start();  // enables output buffering
$foo->bar();  // all output goes only to the buffer
ob_end_clean();  // clears the buffer and ends buffering

Capturing Output into a Variable:

ob_start();  // enables output buffering
$foo->render();  // output goes only to the buffer
$output = ob_get_contents();  // saves the buffer content into a variable
ob_end_clean();  // clears the buffer and ends buffering

The pair ob_get_contents() and ob_end_clean() can be replaced by a single function ob_get_clean(), which removes end from the name but indeed turns off output buffering:

$output = ob_get_clean();  // saves the buffer content into variable and disables buffering

In the given examples, the buffer content did not reach the output at all. If you want to send it to the output instead, you should use ob_end_flush() instead of ob_end_clean(). To simultaneously get the buffer content, send it to the output, and end buffering, there is also a shortcut: ob_get_flush().

You can empty the buffer at any time without ending it using ob_clean() (clears it) or ob_flush() (sends it to the output):

ob_start();  // enables output buffering
$foo->bar();  // all output goes only to the buffer
ob_clean();  // clears the buffer content, but buffering remains active
$foo->render(); // output still goes to the buffer
ob_flush(); // sends the buffer to the output
$none = ob_get_contents();  // the buffer content is now an empty string
ob_end_clean();  // disables output buffering

Output written to php://output is also sent to the buffer, while buffers can be bypassed by writing to php://stdout (or STDOUT), which is available only under CLI, i.e., when running scripts from the command line.

Nesting

Buffers can be nested, so while one buffer is active, calling ob_start() activates a new buffer. Thus, ob_end_flush() and ob_flush() send the buffer content not to the output but to the parent buffer. Only when there is no parent buffer does the content get sent to the actual output, i.e., the browser or terminal.

Therefore, it is important to end buffering, even if an exception occurs during the process:

ob_start();
try {
    $foo->render();
} finally {  // finally available from PHP 5.5
    ob_end_clean(); // or ob_end_flush()
}

Buffer Size

The buffer can also “speed up page generation (I haven't measured this, but it sounds logical)” by not sending every single echo to the browser, but a larger amount of data (e.g., 4kB). Just call at the beginning of the script:

ob_start(null, 4096);

When the buffer size exceeds 4096 bytes (the so-called chunk size), a flush is performed automatically, i.e., the buffer is emptied and sent out. The same can be achieved by setting the output_buffering directive. It is ignored in CLI mode.

But beware, starting buffering without specifying the size, i.e., simply with ob_start(), will cause the page not to be sent gradually but only after it is fully rendered, making the server appear very slow!

HTTP Headers

Output buffering has no effect on sending HTTP headers, which are processed by a different path. However, thanks to buffering, headers can be sent even after some output has been printed, as it is still held in the buffer. This is a side effect you shouldn't rely on, as there is no certainty when the output will exceed the buffer size and be sent.

Security Hole

When the script ends, all unclosed buffers are outputted. This can be considered an unpleasant security hole if, for example, you prepare sensitive data in the buffer not intended for output and an error occurs. The solution is to use a custom handler:

ob_start(function () { return ''; });

Handlers

You can attach a custom handler to output buffering, i.e., a function that processes the buffer content before sending it out:

ob_start(
    function ($buffer, $phase) { return strtoupper($buffer); }
);
echo 'Hello';
ob_end_flush(); // 'HELLO' is sent to the output

Functions ob_clean() or ob_end_clean() will call the handler but discard the output without sending it out. The handler can detect which function is called and respond accordingly. The second parameter $phase is a bitmask (from PHP 5.4):

  • PHP_OUTPUT_HANDLER_START when the buffer is opened
  • PHP_OUTPUT_HANDLER_FINAL when the buffer is closed
  • PHP_OUTPUT_HANDLER_FLUSH when ob_flush() is called (but not ob_end_flush() or ob_get_flush())
  • PHP_OUTPUT_HANDLER_CLEAN when ob_clean(), ob_end_clean(), and ob_get_clean() are called
  • PHP_OUTPUT_HANDLER_WRITE when an automatic flush occurs

The start, final, and flush (or clean) phases can occur simultaneously, distinguished by the binary operator &:

if ($phase & PHP_OUTPUT_HANDLER_START) { ... }
if ($phase & PHP_OUTPUT_HANDLER_FLUSH) { ... }
elseif ($phase & PHP_OUTPUT_HANDLER_CLEAN) { ... }
if ($phase & PHP_OUTPUT_HANDLER_FINAL) { ... }

The PHP_OUTPUT_HANDLER_WRITE phase occurs only if the buffer has a size (chunk size) and that size was exceeded. This is the mentioned automatic flush. Note, the constant PHP_OUTPUT_HANDLER_WRITE has a value of 0, so you can't use a bit test, but:

if ($phase === PHP_OUTPUT_HANDLER_WRITE) { ... }

A handler doesn't have to support all operations. When activating with ob_start(), you can specify the bitmask of supported operations as the third parameter:

  • PHP_OUTPUT_HANDLER_CLEANABLE – allows calling ob_clean() and related functions
  • PHP_OUTPUT_HANDLER_FLUSHABLE – allows calling ob_flush()
  • PHP_OUTPUT_HANDLER_REMOVABLE – buffer can be ended
  • PHP_OUTPUT_HANDLER_STDFLAGS – combines all three flags, the default behavior

This applies even to buffering without a custom handler. For example, if I want to capture the output into a variable, I don't set the PHP_OUTPUT_HANDLER_FLUSHABLE flag, preventing the buffer from being (accidentally) sent to the output with ob_flush(). However, it can still be done with ob_end_flush() or ob_get_flush(), which somewhat defeats the purpose.

Similarly, not setting the PHP_OUTPUT_HANDLER_CLEANABLE flag should prevent the buffer from being cleared, but again it doesn't work.

Finally, not setting PHP_OUTPUT_HANDLER_REMOVABLE makes the buffer user-undeletable; it turns off only when the script ends. An example of a handler that should be set this way is ob_gzhandler, which compresses output, thus reducing volume and increasing data transfer speed. Once this buffer is opened, it sends the HTTP header Content-Encoding: gzip, and all subsequent output must be compressed. Removing the buffer would break the page.

The correct usage is:

ob_start(
    'ob_gzhandler',
    16000, // without chunk size, the server would not send data gradually
    PHP_OUTPUT_HANDLER_FLUSHABLE // but not removable or cleanable
);

You can also enable output compression by setting the zlib.output_compression directive, which turns on buffering with a different handler (not sure how it differs specifically), but it lacks the flag to be non-removable. Since it's good to compress the transfer of all text files, not just PHP-generated pages, it's better to activate compression directly on the HTTP server side.

9 years ago in section PHP | blog written by David Grudl | back to top

You might be interested in

Comments

  1. Kamil #1

    avatar

    Hey,! THANKS i had problem with file_get_contents and empty return with big file jpg and know i get why after migration i forgot increast buffer size, or just setting like your idea!

    6 years ago
  2. akmal #2

    avatar

    nice article, finally i started using this function in my projects

    5 years ago

This article has been closed. It is no longer possible to add comments.


phpFashion © 2004, 2024 David Grudl | o blogu

Ukázky zdrojových kódů smíte používat s uvedením autora a URL tohoto webu bez dalších omezení.