I thought I knew every way to handle output buffering and sending headers() with PHP. But, I just found a new way to break output buffering and get errors when sending headers().
I was doing some more work on my Metro PHP Framework when I noticed the dreaded "Cannot modify output, headers already sent" error. I tried to debug it in the usual way. I added ob_get_level() output and found that it was 1, meaning I had one output buffer active when I tried to send new headers(). But, headers_sent() also returned true. How could this be?
print_r(ob_get_level()); // prints 1
print_r(headers_sent()); // prints 1
What I didn't notice is that was only happening on my login page, a page with a form on it.
I thought this had somethign to do with the base output buffer set in the php.ini, so when I went to look at it, here's what I saw:
; Output buffering is a mechanism for controlling how much output data
; (excluding headers and cookies) PHP should keep internally before pushing that
; data to the client. If your application's output exceeds this setting, PHP
; will send that data in chunks of roughly the size you specify.
; Turning on this setting and managing its maximum buffer size can yield some
; interesting side-effects depending on your application and web server.
; You may be able to send headers and cookies after you've already sent output
; through print or echo. You also may see performance benefits if your server is
; emitting less packets due to buffered output versus PHP streaming the output
; as it gets it. On production servers, 4096 bytes is a good setting for performance
; reasons.
; Note: Output buffering can also be controlled via Output Buffering Control
; functions.
; Possible Values:
; On = Enabled and buffer is unlimited. (Use with caution)
; Off = Disabled
; Integer = Enables the buffer and sets its maximum size in bytes.
; Note: This directive is hardcoded to Off for the CLI SAPI
; Default Value: Off
; Development Value: 4096
; Production Value: 4096
; http://php.net/output-buffering
output_buffering = 4096
Yeah, you can set output_buffering not just to 'On' or 'Off', but to an integer. When the buffer fills up with the bytes specified, it will ob_flush(). Ubuntu (and probably Debian) defaults this to 4096, not just 'On'. The PHP docs also say,
This directive is always Off in PHP-CLI.
So, not only is this setting not helpful in my situation, it would be impossible to test for because PHPUnit uses the CLI SAPI.
Yeah... My login page was longer than 4096 bytes, it got flushed, and my post-output header setting (which normally works when I have gz handler) suddenly started throwing the "Headers already sent" error. The word "flush" didn't exist in my code at all, so it was quite a surprise that PHP would secretly flush output.
So, what's the problem? If you want to adjust the response code to 500 when you encounter errors during templating or output, you might not be able to change the response headers if your page is larger than 4096 on Ubuntu/Debian. (unless you change the default ini)
Do not rely on output buffering to remove headers already sent errors. You must always use the headers_sent() function because output buffers can be flushed without your action.
You learn something new everyday. Maybe this setting isn't new to you, but if it is, hopefully this page saved you some time.