Saturday, 1 February 2014

How to fix “Headers already sent” error in PHP

No output before sending headers!

Functions that send/modify HTTP headers must be invoked before any output is made. Otherwise the call fails:
Warning: Cannot modify header information - headers already sent (output started at file:line)
Some functions modifying the HTTP header are:
Output can be:
  • Unintentional:
  • Intentional:
    • print, echo and other functions producing output (like var_dump)
    • Raw <html> areas before <?php code.

Why does it happen?

To understand why headers must be sent before output it's necessary to look at a typical HTTP*response. PHP scripts mainly generate HTML content, but also pass a set of HTTP/CGI headers to the webserver:
 HTTP/1.1 200 OK
Powered-By: PHP/5.3.7
Vary: Accept-Encoding
Content-Type: text/html; charset=utf-8

<html><head><title>PHP page output page</title></head>
<body><h1>Content</h1> <p>Some more output follows...</p>
and <a href="/"> <img src=about:note> ...
The page/output always follows the headers. PHP is required to pass the headers to the webserver first. It can only do that once. And after the double linebreak it can't ever append to them again.
When PHP receives the first output (print, echo, <html>) it will "flush" the collected headers. Afterwards it can send all the output bits it wants. But sending further headers is impossible from then.

How can you find out where the premature output occured?

The header() warning contains all relevant information to locate the problem source:
Warning: Cannot modify header information - headers already sent by (output started at /www/usr2345/htdocs/auth.php:52) in /www/usr2345/htdocs/index.php on line 100
Here "line 100" refers to the script where the header() invocation failed.
The message in the inner parenthesis is more crucial. It mentions auth.php and line 52 as the source of premature output. One of the typical problem causes will be there:

Print, echo

Intentional output from print and echo statements will terminate the opportunity to send HTTP headers. The application flow must be restructured to avoid that. Use functionsand templating schemes. Ensure header() calls occur before messages are written out.
Functions that can write output include print, echo, printf, trigger_error, vprintf, ob_flush, var_dump, readfile, passthruamong others and user-defined functions.

Raw HTML areas

Unparsed HTML sections in a .php file are direct output as well. Script conditions that will trigger a header() call must be noted before any raw <html> blocks.
<!DOCTYPE html>
<?php
// Too late for headers already.

Whitespace before <?php when "somefile.php line 1" mentioned

If the message says the error is in line 1, then it is typically leading whitespace, text or HTML before the opening <?php marker.
 <?php
# There's a SINGLE! space/newline before <? - Which already seals it.
It can likewise occur for appended scripts or script sections:
?>

<?php
PHP actually eats up a single linebreak after close tags. But it won't compensate multiple newlines or tabs or spaces shifted into such gaps.

UTF-8 BOM

Linebreaks and spaces alone can be a problem. But there are also "invisible" character sequences which can cause this. Most famously the UTF-8 BOM (Byte-Order-Mark)which isn't displayed by most text editors. It's the byte sequence EF BB BF, which is optional and redundant for UTF-8 encoded documents. But the PHP interpreter treats it as raw output. It may show up as the characters  in the output (if the client interprets the document as Latin-1) or similar "garbage".
In particular graphical editors and Java based IDEs are oblivious to its presence. They don't visualize it (obliged by the Unicode standard). Some programmer and console editors however do:
joes editor showing utf-8 bom placeholder, and mc editor a dot
There it's easy to recognize the problem early on. Without such an editor available (or Notepad++ on Windows, which can remedy the problemper menu command), another resort would be a hexeditor. Programmers should have one, as they simplify auditing these kind of issues:
beav hexeditor showing utf-8 bom
The easy fix is to set the text editor to save files as "UTF-8 (no BOM)" or similar such nomenclature. Many newcomers resort to creating new files and just copy&pasting the previous code back in.

Correction utilities

Actually there are automated tools to rewrite text files. For PHP specifically there is the phptags tag tidier. It rewrites close and open tags into long and short forms, but also easily fixes leading and trailing whitespace (and BOM) issues:
 phptags  --whitespace  *.php
It's sane to use on a whole include or project directory.

Whitespace after ?>

If the error source is mentioned as behind the closing ?>then this is where some whitespace or raw text got written out. The PHP end tag does not terminate script executation at this point. Any characters after it will be output as page content still.
It's commonly advised, in particular to newcomers, that trailing ?> PHP close tags should be omitted. This eschews a significant part of these cases. (Quite commonly include() scripts are the culprit.)
Again phptags --whitespace *.php fixes that more easily.
Likewise phptags --unclosed ./includes could drop redundant ?> from scripts.

Error source mentioned as "Unknown on line 0"

It's typically an PHP extension or php.ini setting if no error source is specified. It's occasionally the gzip stream encoding extension or the ob_gzhandler. But it could also be any doubly loaded extension= module, which let to an implicit warning message.

Preceding error messages

If another PHP statement or expression causes a warning message or notice being printeded out, that also counts as premature output.
In this case you need to eschew the error, delay the statement execution, or suppress the message with e.g. isset() or @() - when this doesn't obstruct debugging later on.

No error message

If you have error_reporting or display_errors disabled per php.ini, then no warning will show up. But ignoring errors won't make the problem go away. Headers cannot be sent after premature output still.
So when header("Location: ...") redirects fail siliently it's good to probe for warnings. Reenable them with two simple commands (atop the very first script):
error_reporting(E_ALL);
ini_set("display_errors", 1);
Or set_error_handler("var_dump"); if all else fails.
Speaking of redirect headers, you should often use an idiom like this for final code paths:
exit(header("Location: /finished.html"));
Preferrably even a utility function, which prints a user message in case of header() failure.

Output buffering as workaround

PHPs output bufferingis suitable to alleviate this issue. It often does so quite reliaby, but should be considered strictly a workaround. Its actual purpose is minimizing chunked transfers to the webserver. Restructuring the application to avoid premature output is preferable.
Nevertheless does the output_buffering=setting help. Configure it in the php.ini*or via .htaccess*or even .user.ini*. With that enabled content gets buffered and not instantly passed on to the webserver. Thus HTTP headers can be aggregated.
It can likewise be engaged with a call to ob_start();atop the invocation script. This however is less reliable for a few reasons:
  • Even if <?php ob_start(); ?> starts the first script, whitespace or a BOM can get shuffled before, rendering it ineffective.*
  • It can conceal whitespace for HTML output; but as soon as the application logic attempts to send binary content (a generated image for example), the buffered extraneous spaces become a problem. (Though ob_clean() often is another workaround.)
  • The buffer is limited in size. While usually a hypothetical problem, it might however overrun - which wouldn't be easy to trace.
See also the basic usage examplein the manual, and for more pros and cons:

But it worked on the other server!?

If you didn't get the headers warning before, then the php.ini settinghas been changed. Output buffering then was enabled on the other server, but not on the current. See previous section.

Checking with headers_sent()

You can always use headers_sent() to probe if it's still possible to... send headers. That's useful to conditionally print an info or apply other fallback logic.
if (headers_sent()) {
die("Redirect failed. Please click on this link: <a href=...>");
}
else{
exit(header("Location: /user.php"));
}

HTML <meta> tag workaround

If your application is structurally hard to fix, then an easy (but totally unprofessional) way to allow redirects is injecting HTML. A redirect can be achieved by:
 <meta http-equiv="Location" content="http://example.com/">
Or with a short delay:
 <meta http-equiv="Refresh" content="2; url=../target.html">
This will make your website non-valid (even if faux XHTML) when printed outside the <head> part. Most browsers still accept it. As alternative a Javascript redirectcould do:
 <script> location.replace("target.html"); </script>
It's an acceptable approach if this is used as a fallback by specialized redirect utility functions. Which should first attempt to send a proper header(), but use the meta tag equivalents and a user-friendly message and link as last resort.

Why setcookie() and session_start() are also affected

Both setcookie() and session_start() need to send a Set-Cookie: HTTP header(). The same conditions therefore apply, and similar error messages will be generated with premature output situations.
Header output problems are not the only cause for non-functionality with them of course. Disabled cookies in the browser, or even proxy issues should always be checked. The session functionality also depends on free disk space and other php.ini settings.

Further links

0 comments:

Post a Comment

Twitter Delicious Facebook Digg Stumbleupon Favorites More