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!
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.