流 (stream) 的概念源于 UNIX 中管道 (pipe) 的概念在 UNIX 中, 管道是一条不间断的字节流, 用来实现程序或进程间的通信, 或读写外围设备外部文件等根据流的方向又可以分为输入流和输出流, 同时可以在其外围再套上其它流, 比如缓冲流, 这样就可以得到更多流处理方法
PHP 里的流和 Java 里的流实际上是同一个概念, 只是简单了一点由于 PHP 主要用于 web 开发, 所以流这块的概念被提到的较少如果有 Java 基础, 对于 PHP 里的流就更容易理解了其实 PHP 里的许多高级特性, 比如 SPL, 异常, 过滤器等都参考了 Java 的实现, 在理念和原理上同出一辙
比如下面是一段 PHP SPL 标准库的用法(遍历目录, 查找固定条件的文件):
- class RecursiveFileFilterIterator extends FilterIterator
- {
- // 满足条件的扩展名
- protected $ext = array('jpg','gif');
- /**
- * 提供 $path 并生成对应的目录迭代器
- */
- public function __construct($path)
- {
- parent::__construct(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)));
- }
- /**
- * 检查文件扩展名是否满足条件
- */
- public function accept()
- {
- $item = $this->getInnerIterator();
- if ($item->isFile() && in_array(pathinfo($item->getFilename(), PATHINFO_EXTENSION), $this->ext))
- {
- return TRUE;
- }
- }
- }
- // 实例化
- foreach (new RecursiveFileFilterIterator('D:/history') as $item)
- {
- echo $item . PHP_EOL;
- }
Java 里也有和其同出一辙的代码:
- public class DirectoryContents
- {
- public static void main(String[] args) throws IOException
- {
- File f = new File("."); // current directory
- FilenameFilter textFilter = new FilenameFilter()
- {
- public boolean accept(File dir, String name)
- {
- String lowercaseName = name.toLowerCase();
- if (lowercaseName.endsWith(".txt"))
- {
- return true;
- }
- else
- {
- return false;
- }
- }
- };
- File[] files = f.listFiles(textFilter);
- for (File file : files)
- {
- if (file.isDirectory())
- {
- System.out.print("directory:");
- }
- else
- {
- System.out.print("file:");
- }
- System.out.println(file.getCanonicalPath());
- }
- }
- }
举这个例子, 一方面是说明 PHP 和 Java 在很多方面的概念是一样的, 掌握一种语言对理解另外一门语言会有很大的帮助; 另一方面, 这个例子也有助于我们下面要提到的过滤器流 - filter 其实也是一种设计模式的体现
我们可以通过几个例子先来了解 stream 系列函数的使用
下面是一个使用 socket 来抓取数据的例子:
- $post_ =array (
- 'author' => 'Gonn',
- 'mail'=>'gonn@nowamagic.net',
- 'url'=>'http://www.nowamagic.net/',
- 'text'=>'欢迎访问简明现代魔法');
- $data=http_build_query($post_);
- $fp = fsockopen("nowamagic.net", 80, $errno, $errstr, 5);
- $out="POST http://nowamagic.net/news/1/comment HTTP/1.1\r\n";
- $out.="Host: typecho.org\r\n";
- $out.="User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13"."\r\n";
- $out.="Content-type: application/x-www-form-urlencoded\r\n";
- $out.="PHPSESSID=082b0cc33cc7e6df1f87502c456c3eb0\r\n";
- $out.="Content-Length:" . strlen($data) . "\r\n";
- $out.="Connection: close\r\n\r\n";
- $out.=$data."\r\n\r\n";
- fwrite($fp, $out);
- while (!feof($fp))
- {
- echo fgets($fp, 1280);
- }
- fclose($fp);
我们也可以用 stream_socket 实现, 这很简单, 只需要打开 socket 的代码换成下面的即可:
代码如下:
$fp = stream_socket_client("tcp://nowamagic.net:80", $errno, $errstr, 3);
再来看一个 stream 的例子:
file_get_contents 函数一般常用来读取文件内容, 但这个函数也可以用来抓取远程 url, 起到和 curl 类似的作用
- $opts = array (
- 'http'=>array(
- 'method' => 'POST',
- 'header'=> "Content-type: application/x-www-form-urlencoded\r\n" .
- "Content-Length:" . strlen($data) . "\r\n",
- 'content' => $data)
- );
- $context = stream_context_create($opts);
- file_get_contents('http://nowamagic.net/news/1/comment', false, $context);
注意第三个参数,$context, 即 HTTP 流上下文, 可以理解为套在 file_get_contents 函数上的一根管道同理, 我们还可以创建 FTP 流, socket 流, 并把其套在对应的函数在
更多关于 stream_context_create, 可以参考: PHP 函数补完: stream_context_create()模拟 POST/GET
上面提到的两个 stream 系列的函数都是类似包装器的流, 作用在某种协议的输入输出流上这样的使用方式和概念, 其实和 Java 中的流并没有大的区别, 比如 Java 中经常有这样的写法:
代码如下:
new DataOutputStream(new BufferedOutputStream(new FileOutputStream(new File(fileName))));
一层流嵌套着另外一层流, 和 PHP 里有异曲同工之妙
我们再来看个过滤器流的作用:
- $fp = fopen('c:/test.txt', 'w+');
- /* 把 rot13 过滤器作用在写入流上 */
- stream_filter_append($fp, "string.rot13", STREAM_FILTER_WRITE);
- /* 写入的数据经过 rot13 过滤器的处理 */
- fwrite($fp, "This is a test\n");
- rewind($fp);
- /* 读取写入的数据, 独到的自然是被处理过的字符了 */
- fpassthru($fp);
- fclose($fp);
- // output:Guvf vf n grfg
在上面的例子中, 如果我们把过滤器的类型设置为 STREAM_FILTER_ALL, 即同时作用在读写流上, 那么读写的数据都将被 rot13 过滤器处理, 我们读出的数据就和写入的原始数据是一致的
你可能会奇怪 stream_filter_append 中的 "string.rot13" 这个变量来的莫名其妙, 这实际上是 PHP 内置的一个过滤器
使用下面的方法即可打印出 PHP 内置的流:
- $streamlist = stream_get_filters();
- print_r($streamlist);
输出:
- Array
- (
- [0] => convert.iconv.*
- [1] => mcrypt.*
- [2] => mdecrypt.*
- [3] => string.rot13
- [4] => string.toupper
- [5] => string.tolower
- [6] => string.strip_tags
- [7] => convert.*
- [8] => consumed
- [9] => dechunk
- [10] => zlib.*
- [11] => bzip2.*
- )
自然而然, 我们会想到定义自己的过滤器, 这个也不难:
- class md5_filter extends php_user_filter
- {
- function filter($in, $out, &$consumed, $closing)
- {
- while ($bucket = stream_bucket_make_writeable($in))
- {
- $bucket->data = md5($bucket->data);
- $consumed += $bucket->datalen;
- stream_bucket_append($out, $bucket);
- }
- // 数据处理成功, 可供其它管道读取
- return PSFS_PASS_ON;
- }
- }
- stream_filter_register("string.md5", "md5_filter");
注意: 过滤器名可以随意取
之后就可以使用 "string.md5" 这个我们自定义的过滤器了
这个过滤器的写法看起来很是有点摸不着头脑, 事实上我们只需要看一下 php_user_filter 这个类的结构和内置方法即了解了
过滤器流最适合做的就是文件格式转换了, 包括压缩, 编解码等, 除了这些偏门的用法外, filter 流更有用的一个地方在于调试和日志功能, 比如说在 socket 开发中, 注册一个过滤器流进行 log 记录比如下面的例子:
- class md5_filter extends php_user_filter
- {
- public function filter($in, $out, &$consumed, $closing)
- {
- $data="";
- while ($bucket = stream_bucket_make_writeable($in))
- {
- $bucket->data = md5($bucket->data);
- $consumed += $bucket->datalen;
- stream_bucket_append($out, $bucket);
- }
- call_user_func($this->params, $data);
- return PSFS_PASS_ON;
- }
- }
- $callback = function($data)
- {
- file_put_contents("c:\log.txt",date("Y-m-d H:i")."\r\n");
- };
这个过滤器不仅可以对输入流进行处理, 还能回调一个函数来进行日志记录
可以这么使用:
代码如下:
stream_filter_prepend($fp, "string.md5", STREAM_FILTER_WRITE,$callback);
PHP 中的 stream 流系列函数中还有一个很重要的流, 就是包装类流 streamWrapper 使用包装流可以使得不同类型的协议使用相同的接口操纵数据这个以后再说
来源: https://www.php1.cn/detail/php-dad7bf2c03.html