php runtime and server environment

Output Buffering Configuration

Output buffering means PHP can hold generated output in memory before sending it to the web server or terminal. That can be useful for templates, compression, redirects, tests, and small capture helpers, but it can also hide confusing control flow if it is used carelessly.

A junior PHP developer should understand two things: PHP can be configured to start buffering automatically, and application code can create its own buffers with ob_start().

Runtime configuration

The main setting is output_buffering in php.ini:

output_buffering = 4096
implicit_flush = Off
zlib.output_compression = Off

output_buffering = Off means PHP sends output as soon as it can.

output_buffering = On or a number means PHP keeps output in a buffer first. A number such as 4096 means PHP starts flushing when the buffer reaches that many bytes.

implicit_flush = On tells PHP to flush after every output call. That is unusual for normal web applications and is more common in command-line scripts or streaming situations.

zlib.output_compression = On compresses output before sending it. Compression is often better handled by the web server, reverse proxy, or CDN, so check the deployment before enabling it in PHP.

You can inspect the active values with:

PHP example
<?php

declare(strict_types=1);

$settings = [
    'output_buffering',
    'implicit_flush',
    'zlib.output_compression',
];

foreach ($settings as $setting) {
    echo $setting . ': ' . ini_get($setting) . PHP_EOL;
}

// Prints:
// output_buffering: 4096
// implicit_flush: 0
// zlib.output_compression: 

As with other runtime settings, inspect the same SAPI that handles the request. CLI PHP and PHP-FPM can have different configuration.

Manual output buffers

Application code can start a buffer with ob_start(), write output normally, then read or discard that output.

PHP example
<?php

declare(strict_types=1);

ob_start();

echo '<h1>Account settings</h1>';
echo '<p>Saved successfully.</p>';

$html = ob_get_clean();

echo strlen($html) . PHP_EOL;
echo strip_tags($html) . PHP_EOL;

// Prints:
// 49
// Account settingsSaved successfully.

ob_get_clean() returns the current buffer content and closes that buffer. If you only want to close the buffer and throw the content away, use ob_end_clean(). If you want to send the buffer and close it, use ob_end_flush().

Buffer levels

Buffers can be nested. This matters when frameworks, template engines, or error handlers also use buffering.

PHP example
<?php

declare(strict_types=1);

echo 'Level before: ' . ob_get_level() . PHP_EOL;

ob_start();
echo 'First buffer';

echo 'Level during: ' . ob_get_level() . PHP_EOL;

$captured = ob_get_clean();

echo 'Level after: ' . ob_get_level() . PHP_EOL;
echo $captured . PHP_EOL;

// Prints:
// Level before: 0
// Level after: 0
// First bufferLevel during: 1

The Level during line is captured inside the buffer, so it appears later as part of $captured. This is a common source of confusion when debugging tests or template rendering.

Headers and accidental output

HTTP headers must be sent before the response body. Output buffering can delay the body long enough for header() to work even if something already echoed content.

PHP example
<?php

declare(strict_types=1);

ob_start();

echo 'Preparing response';

if (!headers_sent()) {
    header('X-Lesson: output-buffering');
}

ob_end_clean();

echo 'Response body' . PHP_EOL;

// Prints:
// Response body

This does not mean output buffering should be used to ignore accidental whitespace or random echo statements. If a redirect or JSON response depends on buffering to hide earlier output, the code is usually harder to reason about than it needs to be.

Good uses

Output buffering is useful when:

  • a template file prints HTML and you want to capture it as a string
  • a test needs to assert what a function prints
  • an error page should replace partially prepared output
  • a legacy application needs a small compatibility layer while being refactored
  • a streaming or flushing feature needs deliberate control over when bytes are sent

It is a poor fit when it is used to hide unclear architecture. If a function should return a string, prefer returning a string instead of printing into a buffer and capturing it.

Flushing is not always immediate

Calling flush() or ob_flush() does not guarantee the browser sees content immediately. PHP, PHP-FPM, the web server, proxies, compression layers, and the browser can all buffer data. Streaming features such as progress output, server-sent events, and long-running reports need testing through the real deployment path.

What you should be able to do

After this lesson, you should be able to inspect output buffering settings, capture printed output with ob_start() and ob_get_clean(), explain buffer levels, and recognise when buffering is useful versus when it is hiding messy response flow.

Practice

Task: Capture Template Output

Create a small PHP script that captures printed template output and returns it as a string.

Requirements

  • Write a function named renderPanel() that starts an output buffer.
  • Inside the buffer, print a heading and a paragraph.
  • Return the captured HTML with ob_get_clean().
  • Print the current buffer level before rendering, during rendering, and after rendering.
  • Add a short note explaining when this approach is useful and when returning a string directly would be clearer.

Check your work

The final script should make it obvious that output printed inside the buffer is captured first and only appears when you echo the returned string.

Show solution

One possible solution is to keep the buffer inside a small rendering function, then inspect the buffer level around the call.

PHP example
<?php

declare(strict_types=1);

function renderPanel(string $title, string $message): string
{
    ob_start();

    echo 'Buffer level during render: ' . ob_get_level() . PHP_EOL;
    echo '<section>' . PHP_EOL;
    echo '  <h2>' . htmlspecialchars($title, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</h2>' . PHP_EOL;
    echo '  <p>' . htmlspecialchars($message, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</p>' . PHP_EOL;
    echo '</section>' . PHP_EOL;

    return ob_get_clean();
}

echo 'Buffer level before render: ' . ob_get_level() . PHP_EOL;

$html = renderPanel('Status', 'Profile saved.');

echo 'Buffer level after render: ' . ob_get_level() . PHP_EOL;
echo $html;

// Prints:
// Buffer level before render: 0
// Buffer level after render: 0
// Buffer level during render: 1
// <section>
//   <h2>Status</h2>
//   <p>Profile saved.</p>
// </section>

This approach is useful when working with a template file or legacy view code that prints directly. If the rendering code is new and simple, returning a string or using a proper template engine is often clearer than relying on output buffering.