PHP: The first rule of the output buffering is you do not talk about the output buffering
Tags: php, programming, beginners
Published: 2024-01-21 15:18:39

PHP provides [built-in helpers](https://php.net/manual/en/book.outcontrol.php) to manipulate the output buffering, but it can be tricky to debug.

## What do you mean by "OB"?

I'll use "OB" for "Output Buffering" in this post.

## Why use buffers anyway?

The OB can be convenient for various usages, like aggregating into a string different sources that output HTML in different ways, which removes the hassle of concatenating everything manually.

If you need to capture/inspect/filter the output generated by a third-party code you don't control, the OB may also help. Some frameworks use it to handle includes and other templates. 

Another common trick consists of reusing the same output for different purposes, like displaying the data and sending it through other channels (e.g., emails).

In my experience, problems usually occur with nested buffers or when buffers overrun, which is hard to predict.

## "Headers already sent"

You may be already familiar with the "cannot modify headers, [...] headers already sent" error, which occurs when headers are sent to the browser after the script has begun outputting data.

PHP sends the headers to the server first because of the structure of HTTP responses:

```
HTTP/1.1 200 OK
Vary: Accept-Encoding
Content-Type: text/html; charset utf-8

<html>
    .... etc
```

As a workaround, PHP developers often use the OB to capture the data and send it in one piece to the client (e.g., the browser) afterwards. This trick is heavily used to set headers but also cookies.

## Memory is running out?

Memory leaks sometimes happen with `ob_start()`, as memory usage and latency can increase if you manipulate/dump/log large the objects.

Besides, when the OB is on, built-in functions like `readfile()` won't present memory issues, which makes debugging harder.

## Explicit vs. implicit buffering

Unless you disable it in your [server configuration](https://php.net/manual/en/outcontrol.configuration.php) (e.g., `php_value output_buffering off`), the OB is on. It's called **implicit buffering**.

Using `ob_start()` while another `ob_start()` is still active will start a new level.

## About nested buffers

The OB is stackable, which means:

* `ob_start()` adds an empty buffer on top the stack 
* `ob_get_contents()` returns the content of the active buffer
* `ob_get_clean()` removes the active buffer and returns it

As a result, you might not get what you want because the parent buffer is still active:

> output is being filtered sequentially through each of them in nesting order 

That's why you find the following helper in some `try`/`catch` blocks:

```PHP
try {
    // some code
} catch(Exception $e) {
    // some code

    ob_end_clean();
}
```

IMHO, having multiple levels of buffering is not a common need and should be used _with caution_. 

It does not mean it's _pure evil_, but it can impact error processing and trigger unexpected behaviors if you don't pay attention enough.

## PHP outputs everything when the script ends

Handling sensitive data in the OB can be insecure as PHP writes pending buffers to the output automatically at the end of the script, which may appear convenient but could lead to nasty surprises.

It's probably a bad idea to put such data in the OB, but some developers may want to use it to obscure specific errors.

## The buffer size is limited

The buffer must not exceed the OB limit (4096 bytes by default). Otherwise, it would be flushed unexpectedly, and you may lose control.

## You cannot use the OB in the OB display handlers

The `ob_start()` function accepts callbacks, also called handlers, as its first argument.

This callback is called when the OB is flushed. If you use `ob_start()` inside handlers, you will get this error:

> Cannot use output buffering in output buffering display handlers

Be careful with those callbacks where the _Laws of Physics_ may not apply.

## Wrap up

While the OB is convenient to set cookies and headers quickly, it should not become a general rule, as there are probably better designs.

While the OB may seem appropriate for various usages, it can be tricky when you start nesting things.

Such middle layers can be powerful to capture data but only in very specific cases.