PHP 输出缓冲区

/

2019-4-8

背景:在编写CLI脚本的时候你有没有这样的苦恼
在编写过程中使用了很多 输出语句 打点调试,或者说引用了一些开源组件,组件中有一些输出会污染你的脚本工具输出,如何屏蔽这些输出呢?

脚本示例代码

  1. function foo(){
  2. echo "log message\n";
  3. return "useful message";
  4. }
  5. $message = foo();
  6. echo $message;
  7. //will output:
  8. /* log message
  9. useful message*/

观察脚本代码可以发现,需要屏蔽的是foo()函数中的“log message”

第一个思路是重定向输出流,在foo()前将输出流重定向到某个位置,foo()结束后将输出流恢复,但是恢复操作一直没有头绪,所以只能作罢

第二个思路是在了解了PHP程序的输出机制之后得出的

图片来自:“https://gywbd.github.io/posts/2015/1/php-output-buffer-in-deep.html”

上面这张图片展示了PHP中的三种缓冲区层的逻辑关系。上面的两层就是我们通常所认识到的“输出缓冲区”,最后一个是SAPI中的输出缓冲区。这些都是PHP中的层,当输出的字节离开PHP进入计算机体系结构中的更底层时,缓冲区又会不断出现(终端缓冲区(terminal buffer),fast-cgi缓冲区,web服务器缓冲区,OS缓冲区,TCP/IP栈缓冲区。。。)。请记住一个通用原则,除了这篇文章中讨论的PHP中的情况外,一个软件的很多部分都会先保留信息,然后再把它们传递到下一部分,直到最终把这些信息传递给用户。

而CLI(命令行)下程序的SAPI层有些特殊,会将INI配置中的output_buffer选项强制设置为0,这表示禁用默认PHP输出缓冲区。所以在CLI中,默认情况下你要输出的东西会直接传递到SAPI层,除非你手动调用ob_()类函数。并且在CLI中,implicit_flush的值也会被设置为1。我们经常会搞不清implicit_flush的作用,源代码已说明一切:当implicit_flush被设置为打开(值为1),一旦有任何输出写入到SAPI缓冲区层,它都会立即刷新(flush,意思是把这些数据写入到更低层,并且缓冲区会被清空)。

我们可以通过修改缓冲区的策略来拦截程序输出
由于默认缓冲区和SAPI缓冲区的程序共用性(很多程序都在用)很强,所以我们的屏蔽操作自然是不能在这两层上面,应该自己编写用户缓冲区层。代码如下:

  1. function foo(){
  2. echo "log message\n";
  3. return "useful message";
  4. }
  5. ob_start(function($ctc) { return ; }, 4096);
  6. $message = foo();
  7. ob_get_clean();
  8. echo $message;
  9. //will output:
  10. //useful message

代码浅析:

  1. 在调用foo()前开启缓冲区,在此之后echo 的内容会先进入缓冲区
  2. 拿到message后清空缓冲区,注意是清空不是刷新缓冲区,这样就可以屏蔽我们的“log message”,
  3. ob_start()中的第一个参数是缓冲区填满或溢出时的回调函数,函数的参数是缓冲区内容,起到过滤作用,我们通过 “return ;”,进行清空,若不设置这个函数,缓冲区填满会立刻向下传递,我们的缓冲区就失去了意义。第二个参数是缓冲区大小,我们应根据屏蔽的输出的量来设置成恰当大小,太大了会浪费内存,太小了回调频繁(可能会降低性能?我猜的,这样写回调少一些总归是好的)。

【参考资料】

  1. 深入理解php的输出缓冲区(output buffer)

Reproduced please indicate the author and the source, and error a link to this page.
text link: //sealbaby.cn/php-stdout

Say something...

Website
Username
Email