• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • 位置: php 中文手册 -> php 内置函数

    php 输出缓冲函数

    PHP的输出缓冲区

    缓冲区

    实际上是一个内存地址空间。它用来存储速度不同步的设备或者优先级不同的设备之间传输数据的区域。通过缓冲可以使进程之间的交互时间等待变小,从而使从速度慢的设备读取数据时,速度快的设备的操作进程不发生间断。缓冲区的作用是把输入或者输出的内容先放进内存,而不显示或者读取,最本质的作用就是协调高速CPU和相对缓慢的IO设备(磁盘等)的运作。

    PHP输出缓冲的优点

    默认情况下,使用PHP在通过执行语句生成HTML后,就会立即将其作为数据块发送到浏览器。而,使用PHP输出缓冲,生成的HTML将存储在缓冲区或变量中,并在执行PHP脚本中的最后一个语句后发送到缓冲区以进行渲染。这是性能的显着提高,并且为网页增加了美学价值。

    1. 启用输出缓冲时,开发人员会减少服务器和客户端浏览器之间的交互次数,因为整个HTML会立即发送,因此对于更大的项目,输出缓冲提供了更加节省时间的方法。
    2. 由于缓冲区是将整个HTML存储为字符串,因此我们可以使用所有字符串方法或自定义方法来操作HTML,从而在呈现内容时提供更大的灵活性。
    3. 输出缓冲能够提供更快,更安全,更灵活,更少冗余的渲染方法;我们还可以应用各种压缩方法,从而创建更高效的渲染。
    4. 使用输出缓冲可以更轻松地设置cookie和使用sessions,因为在发送头信息时不包含页面的其余内容。

    PHP输出缓冲的常见函数

    • ob_start()函数:打开输出缓冲。换句话说,它创建了缓冲区(不可见的保持单元格),它将在调用后存储所有输出。
    • ob_get_contents()函数:抓取我们调用ob_start后收集的所有数据,即缓冲区中的所有内容。通常,您将其分配给变量。
    • ob_clean()函数:从输出缓冲区中删除所有内容。请注意,它不输出任何内容。
    • ob_flush()函数:输出缓冲区中的内容。请注意,它不会擦除缓冲区。
    • ob_end_clean()函数:基本上运行ob_get_contents(),擦除缓冲区,并关闭输出缓冲。
    • ob_end_flush()函数:从缓冲区输出内容并结束输出缓冲。它不会擦除缓冲区。

    PHP在执行的时候,在什么地方有用到缓冲区(即OB缓冲区)

    当执行PHP的时候,如果碰到了echoprint_r之类的会输出数据的代码,PHP就会将要输出的数据,放到PHP自身的缓冲区等待输出。当PHP自身的缓冲区接到指令,指示要输出缓冲区的内容时,将会把缓冲区内的数据输出到web服务上(如apache、nignx、IIS等),此处假设为apache服务来讲解。apache接受到PHP输出的数据,然后再把该数据存在到apache自身的缓冲区内等待输出,当apache接受到指令,只是要输出缓冲区的内容时,将会把缓冲区的内容输出,返回到浏览器。

    由此可见,PHP要输出数据的时候,将会经过两个缓冲区(先是自身的,然后是apache的),再返回到用户浏览器。

    • 任何输出内容的函数都会用到输出缓冲区,这是指正常的PHP脚本,如果开发的是PHP扩展,使用的函数(C函数)可能会直接输出写到SAPI缓冲区层,不需要经过输出缓冲层。
    • 输出缓冲区不是唯一用于缓冲输出的层,它实际上只是很多层中的一个,输出缓冲层的行为与使用的SAPI(Web或CLI)有关,不同的SAPI可能有不同的行为。SAPI中的输出缓冲区,这些都是PHP中的层。
    • 当输出的字节离开PHP进入计算机体系结构中的更底层时,缓冲区又会不断出现(终端缓冲区(terminal buffer)、fast-cgi缓冲区、Web服务器缓冲区、操作系统缓冲区、TCP/IP栈缓冲区等)。
    PHPCLI的SAPI有点特殊,CLI也称命令行界面,它会将php.ini配置中output_buffer选项强制设置为0,这表示禁用默认PHP输出缓冲区,所以在CLI中,默认情况下你要输出的内容会直接传递到SAPI层,除非你手动调用ob_()类函数,并且在CLI中,implicit_flush的值也会被设置为1。注:我们经常混淆implicit_flush的作用,php的源代码已说明一切:当implicit_flush被设置为打开(值为1)时,一旦有任何输出写到SAPI缓冲区,它都会立刻刷新(flush,意思把这些数据写到更底层,并且缓冲区会被清空),也就是说任何数据到CLI/SAPI中时,CLI/SAPI都会立即将这些数据仍到它的下一层去,一般会是标准输出管到,write()和fflush()这两个函数就是负责做这件事情的

    默认PHP输出缓冲区

    三个跟缓冲区相关的INI配置选项:output_buffering、implicit_flush、output_handler

    • output_buffering,有3个值,on|off|正整数值,php 5.3+,默认output_buffering = 4096,说明默认输出缓冲区是开启的,大小为4096字节(即4K),当输出缓冲区中的内容超过4096字节时,才会输出缓冲区的内容。也可以用ob_flush来强制输出。而设置为on时,会打开一个无限大的缓冲区。在PHP代码中,写一个ob_start(),表示开启一个无限大的输出缓冲区。
      默认的4k的设置是一个合适的值,这意味着你可以先写入4096个ASCII字符,然后再跟下面的SAPI层通信。并且在web应用环境中,通过socket一个字节一个字节的传输消息的方式对性能并不好。更好的方式是把所有内容一次性传输给服务器,或者至少是一块一块地传输。层与层之间的数据交换的次数越少,性能越好。你应该总是保持输出缓冲区处于可用状态,PHP会负责在请求结束后把它们中的内容传输给终端用户,你不用做任何事情。
    • implicit_flush,默认被设置为关闭(off)。这是正确的设置,因为对于FastCGI协议,刷新操作(flushing)是每次写入后都发送一个FastCGI数组包(packet),如果发送数据包之前先把FastCGI的缓冲区写满会更好一些。如果你想手动刷新SAPI的缓冲区,使用PHP的flush()函数。如果你想写一次就刷新一次,你可以设置INI配置中的implicit_flush选项,或者调用一次ob_implicit_flush()函数。
    • output_handler,是一个回调函数,它可以在缓冲区刷新之前修改缓冲区中的内容。PHP的扩展提供了很多回调函数:
      • ob_gzhandler :使用ext/zlib压缩输出
      • ob_iconv_handler :使用ext/iconv转换字符编码
      • ob_tidyhandler :使用ext/tidy整理输出的HTML文本
      • ob_[inflate/deflate]_handler :使用ext/http压缩输出
      • ob_etaghandler :使用ext/http自动生成HTTP的Etag
      • mb_output_handler :使用ext/mbstring转换字符编码

      缓冲区中的内容会传递给你选择的回调函数(只能用一个)来执行内容转换的工作,所以如果你想获取PHP传输给web服务器以及用户的内容,你可以使用输出缓冲区回调。当前有一点也需要提一下,这里说的“输出”指的是消息头(headers)和消息体(body)。HTTP的消息头也是OB层的一部分。

    消息头和消息体

    当你使用一个输出缓冲区(无论是用户的,还是PHP的)的时候,你可能想以你希望的方式发送HTTP消息头和内容。你知道任何协议都必须在发送消息体之前发送消息头(这也是为什么叫做“头”),但是如果你使用了输出缓冲区层,那么PHP会接管这些,而不需要你操心。实际上,任何跟消息头的输出有关的PHP函数(header(),setcookie(),session_start())都使用了内部的sapi_header_op()函数,这个函数只会把内容写入到消息头缓冲区中。然后当你输出内容是,例如使用print_f(),这些内容会写入到输出缓冲区(假设只有一个)。当这个输出缓冲区中的内容需要被发送时,PHP会先发送消息头,然后发送消息体。PHP为你搞定了所有的事情。如果你觉得不爽,想自己动手,那你就只有把输出缓冲区禁用掉,除此之外别无他法。

    用户输出缓冲区

    默认PHP输出缓冲区运行机制

    对于用户输出缓冲区,我们先通过一个示例来看看它是怎么工作的,以及你可以用它来做什么。再强调一下,如果你想使用默认PHP输出缓冲区层的话,你不能使用CLI,因为它已禁用了这个层。下面的这个示例用的就是默认PHP输出缓冲区,使用了PHP的内部web服务器SAPI:

    /* launched via php -doutput_buffering=32 -dimplicit_flush=1 -S127.0.0.1:8080 -t/var/www */
    echo str_repeat('a', 31);
    sleep(3);
    echo 'b';
    sleep(3);
    echo 'c';
    

    在这个示例中,启动PHP的时候将默认输出缓冲区的大小设置为32字节,程序运行后会先向其中写入31个字节,然后进入睡眠状态。此时屏幕是空的,什么都不会输出,跟预计一样。2秒之后睡眠结束,再写入了一个字节,这个字节填满了缓冲区,它会立即刷新自身,把里面的数据传递给SAPI层的缓冲区,因为我们将implicit_flush设置为1,所以SAPI层的缓冲区也会立即刷新到下一层。字符串’aaaaaaaaaa{31个a}b’会出现在屏幕上,然后脚本再次进入睡眠状态。2秒之后,再输出一个字节,此时缓冲区中有31个空字节,但是PHP脚本已执行完毕,所以包含这1个字节的缓冲区也会立即刷新,从而会在屏幕上输出字符串’c’。

    从这个示例我们可以看到默认PHP输出缓冲区是如何工作的。我们没有调用任何跟缓冲区相关的函数,但这并不意味这它不存在,你要认识到它就存在当前程序的运行环境中(在非CLI模式中才有效)。

    用户的输出缓冲区运行机制

    现在开始讨论用户输出缓冲区,它通过调用ob_start()来实现创建,我们可以创建很多这种缓冲区(至到内存耗尽为止),这些缓冲区组成一个堆栈结构,每个新建缓冲区都会堆叠到之前的缓冲区上,每当它被填满或者溢出,都会执行刷新操作,然后把其中的数据传递给下一个缓冲区。

    ob_start(function($ctc) { static $a = 0; return $a++ . '- ' . $ctc . "\n";}, 10);
    ob_start(function($ctc) { return ucfirst($ctc); }, 3);
    echo "fo";
    sleep(2);
    echo 'o';
    sleep(2);
    echo "barbazz";
    sleep(2);
    echo "hello";
    /* 0- FooBarbazz\n 1- Hello\n */
    

    假设第一个ob_start创建的用户缓冲区为缓冲区1,第二个ob_start创建的为缓冲区2。按照栈的后进先出原则,任何输出都会先存放到缓冲区2中。

    1. 缓冲区2的大小为3个字节,所以第一个echo语句输出的字符串'fo'(2个字节)会先存放在缓冲区2中,还差一个字符,当第二echo语句输出的'o'后,缓冲区2满了,所以它会自动刷新(flush),在刷新之前会先调用第二句ob_start()的回调函数ucfirst(),这个函数会将缓冲区内的字符串的首字母转换为大写,所以输出为'Foo'。然后它会被保存在缓冲区1中,缓冲区1的大小为10。
    2. 第三个echo语句会输出'barbazz',它还是会先放到缓冲区2中,这个字符串有7个字节,缓冲区2已经溢出了,所以它会立即刷新,调用回调函数得到的结果为'Barbazz',然后被传递到缓冲区1中。这个时候缓冲区1中保存了'FooBarbazz',10个字符,缓冲区1会刷新,同样的先会调用第一句ob_start()的回调函数,缓冲区1的回调函数会在字符串前面添加行号,以及在尾部添加一个回车符,所以输出的第一行是'o- FooBarbazz'。
    3. 最后一个echo语句输出了字符串'hello',它大于3个字符,所以会触发缓冲区2刷新,因为此时脚本已执行完毕,所以也会立即刷新缓冲区1,最终得到的第二行输出为'1- Hello'。

    运行时配置

    这些函数的行为受 php.ini 中的设置影响。

    输出控制配置选项
    名字默认可修改范围更新日志
    output_buffering"0"PHP_INI_PERDIR
    output_handlerNULLPHP_INI_PERDIR自 PHP 4.0.4 起可用
    implicit_flush"0"PHP_INI_ALL在 PHP<= 4.2.3 版本中是 PHP_INI_PERDIR
    • output_buffering(boolean/integer):该选项设置为 On 时,将在所有的脚本中使用输出控制。如果要限制输出缓冲区的最大值,可将该选项设定为指定的最大字节数(例如 output_buffering=4096)。
    • output_handler(string):该选项可将脚本所有的输出,重定向到一个函数。例如,将 output_handler 设置为 mb_output_handler()时,字符的编码将被修改为指定的编码。设置的任何处理函数,将自动的处理输出缓冲。
      Note:不能同时使用 mb_output_handler()和 ob_iconv_handler(),也不能同时使用 ob_gzhandler()和 zlib.output_compression。
      Note:只有内置函数可以使用此指令。对于用户定义的函数,使用 ob_start()。
    • implicit_flush(boolean):默认为 FALSE。如将该选项改为 TRUE,PHP 将使输出层,在每段信息块输出后,自动刷新。这等同于在每次使用 print、echo 等函数或每个 HTML 块之后,调用 PHP 中的 flush()函数。不在web环境中使用 PHP 时,打开这个选项对程序执行的性能有严重的影响,通常只推荐在调试时使用。在 CLI SAPI 的执行模式下,该标记默认为 TRUE。