Frenlee

PHP Streams (流)

流是在php4.3.0中引入的,作用是使用同一的方式处理文件,网络和数据压缩等公用的同一套函数和用法的操作。简单而言,就是具有流式行为的资源对象。因此,流可以线性读写,获取还能使用fseek()函数来定位到流中的任意位置。说明文档

处理流函数:fopen()、fgets()、fwrite()、file_get_content();

流封装 协议

流式数据的种类各异,每种类型需要不同独特的协议,以便读写数据。我们将这些协议称为流封装协议(http://www.php.net/manual/zh/wrappers.php).我们可以读写文件系统,可以通过HTTP、HTTPS或SSH与远程web服务器通信,还可以打开并读写zip,rar或则phar压缩文件。这些通信方式包含下述相同的过程:

  1. 开始通信
  2. 读取数据
  3. 写入数据
  4. 结束通信

其协议格式如下:
<sheme>://<target>
其中是流的封装协议,是流的数据源。
PHP已有的协议如下:

自定义流

http://www.php.net/manual/zh/class.streamwrapper.php
http://www.php.net/manual/zh/stream.streamwrapper.example-1.php

1
2
3
4
5
6
7
8
9
10
11
12
file:// — 访问本地文件系统
http:// — 访问 HTTP(s) 网址
ftp:// — 访问 FTP(s) URLs
php:// — 访问各个输入/输出流(I/O streams)
zlib:// — 压缩流
data:// — 数据(RFC 2397)
glob:// — 查找匹配的文件路径模式
phar:// — <span class='wp_keywordlink_affiliate'><a href="http://blog.frenlee.cn/archives/tag/php" title="View all posts in PHP" target="_blank">PHP</a></span> 归档
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音频流
expect:// — 处理交互式的流、

流上下文

有些PHP流能够接受一些列可选参数,这些参数叫流上下文,用于定制流的行为。不同的流封装协议使用的上下文参数有所不同。流上下文使用 stream_content_create() http://php.net/manual/zh/function.stream-context-create.php 函数创建。上下文的创建可以参考流封装协议文档 http://php.net/manual/zh/context.php 。这个函数返回的上下文对象可以传输大多数文件系统和流函数。

下面看个例子吧:
使用filte_get_content()函数创建一个POST请求

1
2
3
4
5
6
7
8
9
10
11
$requestBody = '{"username":"josh"}';
$context = stream_context_create(array(
    'http'=>array(
        'method' => 'POST',
        'header' => "Content-Type:application/json;charset=utf-8;\r\n"  .
            "Content-Length: " . mb_strlen($requestBody),
        'content' => $requestBody
    )
));
$response = file_get_contents('http://localhost/test/get.php'false, $context);
var_dump($response);

流上下文是个关联数组,最外层键是流封装协议的名称。

流过滤器

上面讲过如何去打开流,从其中读取文件。其实,PHP流真正的强大地方在于过滤,转换,添加,删除流中传输的数据。
php自己内置了一些流过滤器:string.rot13,string.toupper,string.tolower,string.strip_tags.
具体查看: http://php.net/manual/zh/filters.php
若要将过滤器附加到现有的流上,要使用stream_filter_append()函数。
示例:
将所有的字母转换为大写

1
2
3
4
5
$handle = fopen('data.txt','rb');
stream_filter_append($handle'string.toupper');
while(feof($handle) !== true) {
    echo fgets($handle).PHP_EOL;
}

我们还可以使用 php://filter 流封装协议吧过滤器附加到流上。
http://php.net/manual/zh/wrappers.php.php#refsect2-wrappers.php-unknown-unknown-unknown-unknown-unknown-unknown-descriptiot
示例:

1
2
3
4
5
$handle = fopen('php://filter/read=string.toupper/resource=data.txt','rb');
while(feof($handle) !== true) {
    echo fgets($handle).PHP_EOL;
}
fclose($handle);

在这个过程中就可以做很多事了,比如说文件的过滤,写入等,都是可以的。

自定义流过滤器

自定义过滤器,这个应该是一个具有高可玩性的功能吧。因为在大多数情况下我们都需要使用自定义的流过滤器来完成我们的业务。

自定义一个流过滤器类,是通过继承 php_user_filter 类来实现的。这个类需要实现filter()方法,然后使用 stream_filter_register()函数来注册自定义的流过滤器。

下面来看一个过滤特殊字符的自定义过滤器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class DirtyWordsFilter extends php_user_filter
{
public function filter($in, $out, &$consumed, $closing)
{
$words = array('你麻痹','SB','sb');//过滤词
$wordData = array();

foreach ($words as $word) {
$replacement = array_fill(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-&gt;data);
$consumed += $bucket-&gt;datalen;
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
}
header('Content-Type:text/html;charset=utf-8');
stream_filter_register('dirty_words_filter', 'DirtyWordsFilter');

$handle = fopen('data.txt', 'rb');
stream_filter_append($handle, 'dirty_words_filter');
while (feof($handle) !== true) {
echo fgets($handle);
}
fclose($handle);

var_dump(stream_get_filters());

过滤器的核心就在filter这个函数,它的作用是接收,处理再转运流数据。

1
2
3
4
$in: 上游流来的一个队列,有一个或者多个片段,片段中是从出发地流出来的数据。一个片段的大小是固定的。
$out: 由一个或者多个片段组成的队列,流向下游的流目的地。
&$consumed: 自定义的过滤器处理的流数据总字节数。
$closing: 判断流是否正常结束,即filter()方法接收到的是最后一个片段。

可以参考流扩展: http://php.net/manual/zh/book.stream.php