[PHP]
output_buffering = 3
这样设置表明输出的 buffer 不超过 3 个字符。
然后重现一下这个 bug:
public function test()
echo 'asd';
header('a: b');
使用 curl 访问一下,返回的 HTTP body 是 asd 和一个 headers already sent 错误信息,curl -I http://localhost/test一下看看 header,发现 a: b 并没有输出到 header 中。
echo 的内容超出了缓冲区限制的长度,便会作为 HTTP body 输出给 WEB 服务器。一旦 echo,PHP 输出 header 的任务就等于结束了,那么此时调用header()就会抛出 headers already sent 的错误。
修改一下代码:
public function test()
header('b: c');
echo 'asd';
header('a: b');
此时输出的 HTTP body 内容是相同的,但是 curl -I 看到的 header 中多了 b: c,说明 echo 之前的header()正确地输出了内容。
setcookie 方法也会发送 header:set-cookie: xxx,所以一样会引起这个问题。
在上面的例子中,我们将 output_buffering 设置为 3,如果 echo 的内容小于 3,是不会引起问题的,因为缓冲区缓冲了 echo 的内容,会在 header 输出之后再输出缓冲内容。在实际的应用中,可以给 output_buffering 一个稍大一些的值。
但是,不能依赖 output_buffering 的大小,应该尽量避免在业务代码中使用 echo 和 print 系函数。
怎样使用 echo
echo 很方便,古董 PHP 开发还会使用 echo 调试大法,而且我们要输出 HTTP 内容肯定要用到 echo 或者 print,怎么可能避免使用呢?
业务代码中尽量避免
我们应该避免在业务中使用,而不是禁止使用。当使用 echo 的时候,因为上述原因出现 headers already sent 错误,要看 output_buffering 设置的大小和 echo 内容的长度,这给 debug 带来了很大的不确定性,测试环境很可能会漏掉这个 case。
在业务中,可能用到 echo 的原因有:1. 调试代码,查看变量;2. 命令行脚本的输出。对于 1,建议通过调试工具调试,或者使用插件 clockwork;对于 2,可以在脚本中通过标准输出来输出重要内容,不需要使用 echo。
fwrite(STDOUT, $content);
如果基于某种原因一定要使用,可以将一段输出用 ob_start 和 ob_end 包裹起来。被包裹的输出会进入内部缓冲区,在需要的时候再 flush 出来。
// ob_start 的函数定义
bool ob_start ([ callable $output_callback = NULL [, int $chunk_size = 0 [, int $flags = PHP_OUTPUT_HANDLER_STDFLAGS ]]])
$chunk_size=0的时候,只有在关闭缓冲区的时候才会输出缓冲区的内容。
public function test()
ob_start(); // 打开缓冲区
echo 'asd';
header('a: b');
ob_end_flush(); // 关闭缓冲区,将缓冲区的内容输出到 HTTP body
一般框架的输出都是这样设计的,echo 会包裹在 ob_start 和 ob_end 之间。
ob_start 的问题
ob_start 不能解决 PHP 代码不规范导致的 headers already sent:
public function test()
ob_start(); // 打开缓冲区
echo 'asd';
header('a: b');
ob_end_flush(); // 关闭缓冲区,将缓冲区的内容输出到 HTTP body
// 这段代码也会报错,因为文件开头不应该有空格或空行
使用 ob_start 需要及时的将数据输出出去,否则可能会因为字符串拼接和二进制内容冲突:
public function test()
ob_start(); // 打开缓冲区
echo 'asd';
imagepng($resource);
ob_end_flush(); // 关闭缓冲区,将缓冲区的内容输出到 HTTP body
// asd 和 imagepng() 的内容混在一起,输出的图片不可用
综上所述,一个良好的实践是:
output_buffering 关闭或者设置一个较小的数值
如非必要,不使用 echo 和 print 系函数
使用 echo 时,尽量用 ob_start 和 ob_end 包裹
使用 ob_start 和 ob_end 包裹时,对自己包裹的内容有清晰的认识,尽量不要跨函数使用 ob_start 和 ob_end