PHP流处理《MordenPHP》

PHP流处理

来自《MordenPHP》的流处理篇

流的作用是在出发地和目的地之间传输数据.
如果读取过文件,就是操作过流.
如:
    php://stdin 从标准输入,读取输入的数据
    php://stdout    从标准输入,输出数据
    file_get_contents();
    fopen();
    fgets();
    fwrite();
    等等,提供了处理不同流资源的统一接口.
    
流封装数据的种类各异,每个类型需要独特的协议.
以便于读写,我们称之为这些为流封装协议.
例如我们读写文件,http&https请求,或ssh连接,
还可以读写,zip,tar等压缩文件.
这些操作都包含下述相同的过程.
1. 开始通信
2. 读取数据
3. 写入数据
4. 结束通信

虽然过程是一样的, 但是读写系统文件和手法http消息的方式不同.
流封装协议的作用是使用通用的接口封装这些差异.

每个流都有一个协议和目标,指定协议的和目标的方法是使用流标识符,格式如下:
    <scheme>://<targett>
    scheme为封装协议
    target为流的数据源
示例:
    使用HTTP流分装协议与api通信
    <?php
        $json = file_get_contents(
            'http://www.baidu.com'
        );
不要以为这是普通的网页URL,file_get_contents();
函数的字符串, 其实是一个流标识符.
http协议会让PHP使用http流封装协议.这个参数中,
http之后是流的目标,流的目标之所以看起来像是普通的URL.
是因为http流封装协议就是这样的规定.其他流封装协议可能不是这样

file://流封装协议

我们使用file_get_contents();fopen();fwrite();fgets();等等
函数读写系统文件,因为PHP默认使用流封装协议是file://,
所以我们很少认为这些函数使用的时PHP流.

隐式使用file://流封装协议

<?php
    $handle = fopen('/etc/hosts','rb');
    while(fed($handle) != true){
        echo fgets($handle);
    }
    flose($handle);

显式使用file://流封装协议

<?php
    //我们通常会省略file://因为这是PHP使用的默认值
    $handle = fopen('file:///etc/hosts','rb');
    while(fed($handle) != true){
        echo fgets($handle);
    }
    flose($handle);

PHP://流封装协议

php:// — 访问各个输入/输出流(I/O streams)

  • php://input
php://input 是个可以访问请求的原始数据的只读流。 
POST 请求的情况下,最好使用 php://input 来代替 $HTTP_RAW_POST_DATA,因为它不依赖于特定的 php.ini 指令。
而且,这样的情况下 $HTTP_RAW_POST_DATA($_POST) 默认没有填充, 比激活 always_populate_raw_post_data 潜在需要更少的内存。 
enctype="multipart/form-data" 的时候 php://input 是无效的。

$HTTP_RAW_POST_DATA在PHP7中已被弃用所以可以用file_get_contents("php://input");

  • php://output
php://output 是一个只写的数据流, 允许你以 print 和 echo 一样的方式 写入到输出缓冲区。

  • php://stdin
    这个是只读的PHP流其中数据来自标准输入.
    如:PHP脚本可以用这个流接收命令行转入脚本的信息.
  • php://stdout
    这个PHP流的作用是把数据写入到当前的缓冲区.
    这个流只能写,无法读取或寻址.
  • php://memory
    这个PHP流的作用是从系统内存中读取数据,或者把书写入系统内存.
    这个PHP流的缺点是可用内存有限.
    使用php://temp更安全
  • php://temp
    这个PHP流的作用和php://memory类似,不过没有可用内存时,PHP会把数据写入临时文件.
    

自定义流封装协议

我们还可以自己编写PHP流封装协议,PHP提供了一个streamWrapper类,演示如何编写自定义的流封装协议.

流的上下文

有些PHP流能接受一系列的可选参数,这些参数叫流上下文.
用于定制流的行为.不同的流封装协议使用的上下文参数也有所不同.
流上下文使用 stream_context_create()函数创建.
这个函数返回的上下文对象可以传入大多数文件系统和流函数.
流上下文是一个关联数组,最外层键是刘封装协议的名称.
上下文数组中的值,针对每个具体的流封装协议.

示例: 流上下文

<?php
    $requestBody = '{'username':'jjj'}';
    $context = stream_context_create([
        'http' = [
            'method'=>'post',
            'header'=>'Content-Type:application/json;charset=utf-8;\r\n'.
                        'Content-Length:'.mb_strlen($reqeustBody),
            'content'=>$requestBody,
        ]
    ]);
    $response = file_get_contents('http://myhost.com', false, $context);

流过滤器

PHP中的流真正强大的地方在于,过滤,转换,添加,删除流中的数据.
例如我们开一个流处理Markdown文件, 在把文件内容读入内存的过程中,
自动将其转换为HTML.

注意

PHP内置了几个流过滤器,
string.rot13,string.toupper,string.tolower和string.strip_tags;
通过输出stream_get_filters()可以查看支持的内置过滤器
这些过滤器,没什么用. 我们要自定义过滤器.

若想把过滤器附加到现有的流上,要使用stream_filter_append()函数, 下面的案例是从本地文件中读取数据,同时使用了string.toupper过滤器. 目的是把文件所有的内容转换为大写字母. 我不建议这么写, 下面这里也只是做演示而已.

$handle = fopen("data.txt","rb");
stream_filter_append($handle, "string.toupper");
while (feof($handle) !== true) {
    echo fgets($handle);   //输出的全是大写字母
}
fclose($handle);

我们还可以使用php://filter流封装协议将过滤器附加到流上,不过使用这种方法之前, 要先打开PHP流.

$handle = fopen('php://filter/read=string.toupper/resource=data.txt','rb');
while (feof($handle) !== true) {
    echo fgets($handle);   //输出的全是大写字母
}
fclose($handle);

我们要特别注意fopen()函数的第一个参数,这个参数的值是php://流封装协议的流标识符.这个流标识符中的目标如下所示

filter/read=<filter_name>/resource=<scheme>://<target>

这种方式和stream_filter_append();函数相比较为繁琐.可是PHP的某些文件系统函数, 在调用后无法附加过滤器.例如:file()和fpassthru().所以这些函数只能使用php://filter流封装协议附加流过滤器

下面是一个更加实用的案例 我们内部的内容管理系统会把NGINX访问日志保存到rsync.net,我们把一天的的访问情况保存到一个日志文件中. 而且会用bz2压缩每一个日志文件.日志文件的名称使用YYYY-MM-DD.log.bz2格式. 领导让我提取过去30天的某个域名的访问数据. 我要计算日期范围,确定日志文件的名称,通过ftp连接rsync.net,下载文件,解压缩文件,逐行迭代每个文件,把响应的行取出来.然后把访问数据写入一个输出目标.

<?php
$dateStart    = new \DateTime();
$dateInterval = \DateInterval::createFromDateString('-1 day');
$datePeriod   = new \DatePeriod($dateStart, $dateInterval, 30);
foreach ($datePeriod as $date) {
    $file = 'sftp://USER:PASS@rsync.net/' . $date->format('Y-m-d') . '.log.bz2';
    if (file_exists($file)) {
        $handle = fopen($file, 'rb');
        stream_filter_append($handle, 'bzip2.decompress');
        while (feof($handle) !== true) {
            $line = fgets($handle);
            if (strpos($line, 'www.example.com') !== false) {
                fwrite(STDOUT, $line);
            }
        }
        fclose($handle);
    }
}

//2-4行创建有一个连续30天的DataPeriod实例,一天一天反向向前推.
//6行使用每次迭代DataPeriod实例,得到DateTime实例创建日志文件的文件名.
//8-9行使用sftp流封装协议打开位于rsync.net上的日志文件流资源.我们把bzip2.decompress流顾虑器附加到日志文件流资源上.实时解压缩bzip2格式的日志文件
//10-15行使用PHP原生的文件系统函数迭代解压缩后的日志文件
//12-14行检查各行日志,看访问的是不是指定域名,如果是把这一行日志写入到标准输出.

自定义流过滤器

我们还可以编写自定义的流过滤器.其实,大多数情况下都要用自定义的流过滤器.
自定义的流过滤器是一个PHP类,扩展内置的php_user_filter类.
这个类必须实现filter(),onCreate()和onClose()方法.
而且,必须使用 stream_filter_register() 函数注册自定义的流过滤器.

注意: 桶排场一排流过来.

PHP流会把数据分成按照次序排列的桶, 一个桶中盛放的流数据时固定的(例如4096字节).
如果还用管道比喻, 就是把水放在一个个水桶中个, 顺着管道 从出发地漂流到目的地,在漂亮的过程中,会经过流过滤器.
流过滤器一次能接受并处理一个或多个桶.一定时间内过滤器接收到的桶叫做桶队列.

下面我们自定义一个流过滤器,把流中的数据读入内存时审查其中的脏字. 首先, 我们必须建立一个PHP类,让他扩展php_user_filter类. 这个类必须实现filter()方法.这个方法是一个筛子, 用于过滤流经的桶.这个方法的参数是上游流来的桶队列. 处理经过队列中的每个桶对象后.再把桶排成一排, 向下游的目的地漂去. 我们自定义的额DirtyWordsFilter流处理器如下所示

//自定义的DirtyWordsFilter流过滤器
class DirtyWordsFilter extends php_user_filter
{

    /**
     * Method  filter
     *
     * @desc    ......
     * @author  liuhao <lh@btctrade.com>
     * @date    2018/10/10
     * @time    16:16
     *
     * @param resource $in       流来的桶队列
     * @param resource $out      流走的桶队列
     * @param int      $consumed 处理的字节数
     * @param bool     $closing  是流中最后一个桶队列吗?
     *
     * @return  int
     */
    public function filter($in, $out, &$consumed, $closing)
    {
        $words    = ['grime', 'dirt', 'grease'];
        $wordData = [];
        foreach ($words as $word) {
            $replacement     = array_filter(0, mb_strlen($word), '*');
            $wordData[$word] = implode('', $replacement);
        }
        $bad  = array_keys($wordData);
        $good = array_values($wordData);
        //迭代流来的桶队列中的每个桶
        while ($bucket = stream_bucket_make_writeable($in)) {
            //审查桶数据中的脏字
            $bucket->data = str_replace($bad, $good, $bucket->data);
            //增加已处理的数据量
            $consumed += $bucket->datalen;
            //把桶放入流向下游的队列中
            stream_bucket_append($out, $bucket);
        }

        return PSFS_PASS_ON;
    }
}

上面的PHP类的简单解析 filter()方法的作用是接收, 处理再转运桶中的流数据. 在filter()方法中,我们迭代桶队列$in中的桶, 把脏字替换成审核后的值. 这个方法的返回值是PSFS_PASS_ON常量. 表示操作成交, 这个方法接收4个参数:

$in 上游流来的一个队列,有一个或多个桶,桶中是从出发地流来的数据. $out 由一个桶或者多个桶组成的队列, 向下游的流目的地. $consumed 自定义的过滤器处理的流数据总字节数. $closing filter()方法接收的时最后一个桶队列吗

然后我们必须使用stream_filter_register()函数注册这个自定义的DirtWordsFilter过滤器 如下:

<?php
    stream_filter_register('dirty_words_filter','DirtyWrodsFilter');

第一个参数是用于识别这个自定义过滤器的过滤器名. 第二个参数是这个自定义过滤器的类名.

现在可以使用这个自定义过滤器了, 如下所示:

<?php
//使用DirtyWordsFilter流处理器
$handle = fopen('data.txt','rb');
stream_filter_append($handle, 'dirty_word_filter');
while(feof($handle) !== true){
    echo fgets($handle);    //输出审查后的文本
}
fclose();

将获取到的流进行base64

之前在做OTC项目中需要将图片做成base64,作为接口参数返回

但是GD2的imagejpeg()/imagepng()等函数生成资源时不返回任何数据,它们直接将图像数据写入输出流(或文件)。

如果希望捕获编码为base64的数据,最简单的方法是使用PHP输出控制函数,然后在$image_data上使用base64_encode。

//来源: https://stackoverflow.com/questions/8551754/convert-gd-output-to-base64

//todo 这里是验证码图片的流程

//这里是在图片输出时缓冲在内存中, 一次性拿到, 然后base64一下
ob_start (); 

  imagejpeg ($img);
  $image_data = ob_get_contents (); 

ob_end_clean (); 

$image_data_base64 = base64_encode ($image_data);

imagedestroy($img)

转载于:https://my.oschina.net/chinaliuhan/blog/3064493

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值