PHP 5.6: headers_sent 间歇 returns 真,空文件名,第 0 行

PHP 5.6: headers_sent intermittently returns true, empty file name, and line 0

我的 PHP 脚本(PHP 5.6,Apache 2.2)间歇性地遇到这个问题:

Warning: Cannot modify header information - headers already sent in /path/to/index.php on line 55

此警告没有我在其他相关问题中看到的 "sent by" 部分,因此我在有问题的 header()setcookie() 调用之前添加了这段代码:

if (headers_sent($filename, $linenum)){
    echo("Output buffer: #" . ob_get_contents() . "#");
    echo "Headers already sent in $filename on line $linenum: ";
    print_r(headers_list());
}

这是问题发生时我得到的输出:

Output buffer: ##
Headers already sent in on line 0:
Array (
  [0] => X-Powered-By: PHP/5.6.23
  [1] => Content-type: text/html; charset=UTF-8
)

(旁注:我在 php.ini 中将 output_buffering 设置为 4096 字节,所以这两个 headers 中的 63 个字符不应该被缓冲并等待更多,而不是过早发送?)

这个问题是在我第一次启动包含网络服务器的 Docker 容器时出现的。之后,它只会(但不是 总是 )在我一段时间内(可能一两个小时)第一次访问我的网站时发生,当我调用 header()setcookie() 登录用户或重定向到登录页面。

我已经反复阅读 this answer 到一般 "Headers already sent" 错误,而且,我已尽我所能排除了这些可能的原因:

  1. HTML 在我调用 setcookie()header()
  2. 之前阻止或调用 printecho
  3. 我的 PHP 标签外的空格
  4. 物料清单
  5. auto_prepend_file php.ini 设置
  6. gzip 流编码 - 安装了 zlib,但 zlib.output_compression 已关闭
  7. 重复 extension= php.ini 设置

那个答案提到

It's typically a PHP extension or php.ini setting if no error source is concretized.

所以,我现在正在查看我的扩展...get_loaded_extensions 给了我一个 51 长度的数组,其中包含这些条目:

Core, date, ereg, libxml, openssl,
pcre, zlib, filter, hash, Reflection,
SPL, session, standard, apache2handler, bz2,
calendar, ctype, curl, dom, exif,
fileinfo, ftp, gd, gettext, iconv,
mysqlnd, PDO, Phar, posix, shmop,
SimpleXML, snmp, soap, sockets, sqlite3,
sysvmsg, sysvsem, sysvshm, tokenizer, xml,
xmlwriter, xsl, mysql, mysqli, pdo_mysql,
pdo_sqlite, wddx, xmlreader, json, zip, mhash

我没有使用所有这些,所以我打算仔细检查并删除未使用的,希望其中一个是导致问题的原因。

Worst-case 场景,我会尝试将我的 output_buffering 值或使用 ob_start()ob_end_flush() 到我的文件的开头和结尾。我不知道为什么当我当前的 output_buffering 值 4096 没有解决这个问题时,我知道这个解决方法有它自己的问题。

我在这里遗漏了什么——我需要检查其他可能的原因吗?我应该尝试不同的 PHP 版本,还是 运行 我的代码子集在没有扩展的干净 PHP 安装上?

编辑:添加了 ob_get_contents() 调用和输出,以及关于能够通过启动新的 Docker 容器来持续重现它的信息。删除了关于我的 error_reporting 值的信息;改变这个只会发现一个 always_populate_raw_post_data 弃用通知,修复它对这里描述的问题没有影响。

我以前见过由于行结束格式引起的问题。您要将文件从一个操作系统环境移动到另一个操作系统环境吗?

有时隐藏的回车符 return (/r) 或其他特殊的白色-space 字符会导致此问题但在文件中不可见。

它可以是 UTF8 BOM 字符,或者错误的换行符、不可打印的字符、错误的 php 关闭标记(我不使用它们,以防止结束行问题)。即使没有这样的问题,即使您尽一切努力防止它发生,它也可能(并且将会)发生。问题可能与 FTP 服务器、文本编辑器、错误的 PHP 配置、编码、错误的 cgi 配置有关。为了防止所有这些我使用空输出缓冲区,只需在第一个 php 文件

中开始缓冲

index.php:

if(version_compare(PHP_VERSION, '7.0.0') >= 0 || ob_get_level() < 1)
    ob_start();

HtmlResponce.php:

ob_end_clean();
echo $this->getRenderedContent();

FileResponce.php:

ob_end_clean();
readfile($this->content);

在 php5 和 php7 输出缓冲有一些不同,输出缓冲也可以在 php.ini 中配置并且在 cgi 和 apache mod 配置中有差异.您不应该依赖 "default" 配置。要处理警告消息,请使用 set_error_handler() 您也将使用 ob_end_clean()

删除它们

如果您开始新项目,请查看 symfony components,我在我的 CMS 中使用了很多。这是您避免因奇怪的 PHP 错误而失眠的机会。

删除一些未使用的遗留代码后——包括 require_once 对一个全部是遗留代码的文件的调用——此问题不再发生。它可能会在我最初观察到的 "intermittent" 条件下重复出现,但我的重现它的方法不再出现此问题。

我不知道为什么这似乎有帮助 - 删除的行完全在 <php? ?> 标签内,我检查了删除的文件是否有标签、BOM 和 CRLF 之外的空白。

我也不知道为什么这个问题时断时续;如果它与删除的代码或文件有关,它应该每次都发生。

感谢大家的评论和回答!