PHP的缓冲区你了解过吗( 三 )


缓冲区2的大小为3个字节,所以第一个echo语句输出的字符串'fo'(2个字节)会先存放在缓冲区2中,还差一个字符,当第二echo语句输出的'o'后,缓冲区2满了,所以它会刷新(flush),在刷新之前会先调用ob_start()的回调函数,这个函数会将缓冲区内的字符串的首字母转换为大写,所以输出为'Foo' 。然后它会被保存在缓冲区1中,缓冲区1的大小为10 。
第三个echo语句会输出'barbazz',它还是会先放到缓冲区2中,这个字符串有7个字节,缓冲区2已经溢出了,所以它会立即刷新,调用回调函数得到的结果为'Barbazz',然后被传递到缓冲区1中 。这个时候缓冲区1中保存了'FooBarbazz',10个字符,缓冲区1会刷新,同样的先会调用ob_start()的回调函数,缓冲区1的回调函数会在字符串前面添加行号,以及在尾部添加一个回车符,所以输出的第一行是'o-%20FooBarbazz' 。
最后一个echo语句输出了字符串'hello',它大于3个字符,所以会触发缓冲区2刷新,因为此时脚本已执行完毕,所以也会立即刷新缓冲区1,最终得到的第二行输出为'1-%20Hello' 。
输出缓冲区的内部实现
自5.4版后,整个缓冲区层都被重写了(由Michael%20Wallner完成) 。之前的代码很垃圾,很多事情都做不了,并且有很多bug 。这篇文章会给你提供更多相关信息 。所以PHP%205.4才会对这部分进行重新,现在的设计更好,代码也更整洁,添加了一些新特性,跟5.3版的不兼容问题也很少 。赞一个!
其中最赞的一个特性是扩展可以声明它自己的输出缓冲区回调与其他扩展提供的回调冲突 。在此之前,这是不可能的,之前如果要开发使用输出缓冲区的扩展,必须先搞清楚所有其他提供了缓冲区回调的扩展可能带来的影响 。
下面是一个简单的示例,它展示了怎样注册一个回调函数来将缓冲区中的字符转换为大写,这个示例的代码可能不是很好,但是足以满足我们的目的:
#ifdef%20HAVE_CONFIG_H#include%20"config.h"#endif#include%20"php.h"#include%20"php_ini.h"#include%20"main/php_output.h"#include%20"php_myext.h"static%20int%20myext_output_handler(void%20**nothing,%20php_output_context%20*output_context){%20char%20*dup%20=%20NULL;%20dup%20=%20estrndup(output_context->in.data,%20output_context->in.used);%20php_strtoupper(dup,%20output_context->in.used);%20output_context->out.data%20=%20dup;%20output_context->out.used%20=%20output_context->in.used;%20output_context->out.free%20=%201;%20return%20SUCCESS;}PHP_RINIT_FUNCTION(myext){%20php_output_handler%20*handler;%20handler%20=%20php_output_handler_create_internal("myext%20handler",%20sizeof("myext%20handler")%20-1,%20myext_output_handler,%20/*%20PHP_OUTPUT_HANDLER_DEFAULT_SIZE%20*/%20128,%20PHP_OUTPUT_HANDLER_STDFLAGS);%20php_output_handler_start(handler);%20return%20SUCCESS;}zend_module_entry%20myext_module_entry%20=%20{%20STANDARD_MODULE_HEADER,%20"myext",%20NULL,%20/*%20Function%20entries%20*/%20NULL,%20NULL,%20/*%20Module%20shutdown%20*/%20PHP_RINIT(myext),%20/*%20Request%20init%20*/%20NULL,%20/*%20Request%20shutdown%20*/%20NULL,%20/*%20Module%20information%20*/%20"0.1",%20/*%20Replace%20with%20version%20number%20for%20your%20extension%20*/%20STANDARD_MODULE_PROPERTIES};#ifdef%20COMPILE_DL_MYEXTZEND_GET_MODULE(myext)#endif陷阱
大部分陷阱都已经揭示出来了 。有一些是逻辑的问题,有一些是隐藏的 。逻辑方面,最明显的是你不应该在输出缓冲区回调函数内调用任何缓冲区相关的函数,也不要在回调函数中输出任何东西 。
相对不太明显的是有些PHP的内部函数也使用了输出缓冲区,它们会叠加到其他的缓冲区上,这些函数会填满自己的缓冲区然后刷新,或者是返回里面的内容 。print_r()、highlight_file()和highlight_file::handle()都是这类函数 。你不应该在输出缓冲区的回调函数中使用这些函数 。这种行为会导致未定义的错误,或者至少得不到你期望的结果 。
总结
输出层(output%20layer)就像一个网,它会把所有从PHP”遗漏“的输出圈起来,然后把它们保存到一个大小固定的缓冲区中 。当缓冲区被填满了的时,里面的内容会刷新(写入)到下一层(如果有的话),或者是写入到下面的逻辑层:SAPI缓冲区 。开发人员可以控制缓冲区的数量、大小以及在每个缓冲区层可以执行的操作(清除、刷新和删除) 。这种方式非常灵活,它允许库和框架设计者可以完全控制它们自己输出的内容,并把它们放到一个全局的缓冲区中 。对于输出,我们需要知道任何输出流的内容和任何HTTP消息头,PHP都会以正确的顺序发送它们 。


推荐阅读