PHP流Streams、包装器wrapper概念与用法实例详解


Posted in PHP onNovember 17, 2017

本文实例讲述了PHP流Streams、包装器wrapper概念与用法。分享给大家供大家参考,具体如下:

流Streams这个概念是在php4.3引进的,是对流式数据的抽象,用于统一数据操作,比如文件数据、网络数据、压缩数据等,以使可以共享同一套函数,php的文件系统函数就是这样的共享,比如file_get_contents()函数即可打开本地文件也可以访问url就是这一体现。简单点讲,流就是表现出流式数据行为的资源对象。

以线性方式进行读写,并可以在流里面任意位置进行搜索。

流有点类似数据库抽象层,在数据库抽象层方面,不管使用何种数据库,在抽象层之上都使用相同的方式操作数据,而流是对数据的抽象,它不管是本地文件还是远程文件还是压缩文件等等,只要来的是流式数据,那么操作方式就是一样的。

有了流这个概念就引申出了包装器wrapper这个概念,每个流都对应一种包装器,流是从统一操作这个角度产生的一个概念,而包装器呢是从理解流数据内容出发产生的一个概念,也就是这个统一的操作方式怎么操作或配置不同的内容;

这些内容都是以流的方式呈现,但内容规则是不一样的,比如http协议传来的数据是流的方式,但只有http包装器才理解http协议传来的数据的意思,可以这么理解,流就是一根流水的管子,只不过它流出的是数据,包装器就是套在流这根管子外层的一个解释者,它理解流出的数据的意思,并能操作它。

官方手册说:“一个包装器是告诉流怎么处理特殊协议或编码的附加代码”明白这句话的意思了吗?

包装器可以嵌套,一个流外面包裹了一个包装器后,还可以在外层继续包裹包装器,这个时候里层的包装器相对于外层的包装器充当流的角色
在php自身底层实现的c语言开发文档有这样的解释:

流API操作一对不同级别:在基本级别,api定义了php_stream对象表示流式数据源,在稍微高一点的级别,api定义了php_stream_wrapper对象。

它包裹低一级别的php_stream对象,以提供取回URL的内容和元数据、添加上下文参数的能力,调整包装器行为;

每一种流打开后都可以应用任意数量的过滤器在上面,流数据会经过过滤器的处理,笔者认为过滤器这个词用得有点不准确,有些误导人。

从字面意思看好像是去掉一些数据的感觉,应该称为数据调整器,因为它既可去掉一些数据,也可以添加,还可以修改,但历史原因约定俗成,也就称为过滤器了,大家心里明白就好。

我们经常看到下面的词,来解释下他们的区别:

资源和数据:资源是比较宏观的说法,通常包含数据,而数据是比较具象的说法,在开发程序的时候经常说是数据,而在软件规划时说是资源,他们是近义词,就像软件设计和程序开发的区别一样。

上下文和参数:上下文是比较宏观的说法,经常用在沟通上面,具体点讲就是一次沟通本身的参数,而参数这个说法往往用在比较具体的事情上面,比如说函数

上面解释了概念性的东西,下面来看看具体内容:

php支持的协议和包装器请看这里:http://php.net/manual/zh/wrappers.php:
(笔者注:原标题是:支持的协议和封装协议,中文翻译有点误导,准确的讲就是支持的协议和包装器,从英文版面就很清楚)
默认的支持了一些协议和包装器,请用stream_get_wrappers()函数查看.也可以自定义一个包装器,用stream_wrapper_register()注册
尽管RFC 3986里面可以使用:做分割符,但php只允许://,所以url请使用"scheme://target"这样的格式

file:// — 访问本地文件系统,在用文件系统函数时默认就使用该包装器
http:// — 访问 HTTP(s) 网址
ftp:// — 访问 FTP(s) URLs
php:// — 访问各个输入/输出流(I/O streams)
zlib:// — 压缩流
data:// — 数据(RFC 2397)
glob:// — 查找匹配的文件路径模式
phar:// — PHP 归档
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音频流
expect:// — 处理交互式的流

如何实现一个自定义的包装器:

在用fopen、fwrite、fread、fgets、feof、rewind、file_put_contents、file_get_contents等等文件系统函数操作流时,数据是先传给定义的包装器类对象,包装器再去操作流。
如何实现一个自定义的流包装器呢?php提供了一个类原型,只是原型而已,不是接口也不是类,不能用于继承:

streamWrapper {
/* 属性 */
public resource $context ;
/* 方法 */
__construct ( void )
__destruct ( void )
public bool dir_closedir ( void )
public bool dir_opendir ( string $path , int $options )
public string dir_readdir ( void )
public bool dir_rewinddir ( void )
public bool mkdir ( string $path , int $mode , int $options )
public bool rename ( string $path_from , string $path_to )
public bool rmdir ( string $path , int $options )
public resource stream_cast ( int $cast_as )
public void stream_close ( void )
public bool stream_eof ( void )
public bool stream_flush ( void )
public bool stream_lock ( int $operation )
public bool stream_metadata ( string $path , int $option , mixed $value )
public bool stream_open ( string $path , string $mode , int $options , string &$opened_path )
public string stream_read ( int $count )
public bool stream_seek ( int $offset , int $whence = SEEK_SET )
public bool stream_set_option ( int $option , int $arg1 , int $arg2 )
public array stream_stat ( void )
public int stream_tell ( void )
public bool stream_truncate ( int $new_size )
public int stream_write ( string $data )
public bool unlink ( string $path )
public array url_stat ( string $path , int $flags )
}

在这个原型里面定义的方法,根据自己需要去定义,并不要求全部实现,这就是为什么不定义成接口的原因,因为有些实现根本用不着某些方法,
这带来很多灵活性,比如包装器是不支持删除目录rmdir功能的,那么就不需要实现streamWrapper::rmdir
由于未实现它,如果用户在包装器上调用rmdir将有错误抛出,要自定义这个错误那么也可以实现它并在其内部抛出错误
streamWrapper也不是一个预定义类,测试class_exists("streamWrapper")就知道,它只是一个指导开发者的原型

官方手册提供了一个例子:http://php.net/manual/zh/stream.streamwrapper.example-1.php

本博客提供一个从drupal8系统中抽取修改过的包装器例子,请看drupal8源码分析关于流那一部分

流系列函数,官方手册:http://php.net/manual/zh/ref.stream.php

常用的函数如下:

stream_bucket_append函数:为队列添加数据 
stream_bucket_make_writeable函数:从操作的队列中返回一个数据对象
stream_bucket_new函数:为当前队列创建一个新的数据
stream_bucket_prepend函数:预备数据到队列 
stream_context_create函数:创建数据流上下文
stream_context_get_default函数:获取默认的数据流上下文
stream_context_get_options函数:获取数据流的设置
stream_context_set_option函数:对数据流、数据包或者上下文进行设置
stream_context_set_params函数:为数据流、数据包或者上下文设置参数
stream_copy_to_stream函数:在数据流之间进行复制操作
stream_filter_append函数:为数据流添加过滤器
stream_filter_prepend函数:为数据流预备添加过滤器
stream_filter_register函数:注册一个数据流的过滤器并作为PHP类执行
stream_filter_remove函数:从一个数据流中移除过滤器
stream_get_contents函数:读取数据流中的剩余数据到字符串
stream_get_filters函数:返回已经注册的数据流过滤器列表
stream_get_line函数:按照给定的定界符从数据流资源中获取行
stream_get_meta_data函数:从封装协议文件指针中获取报头/元数据
stream_get_transports函数:返回注册的Socket传输列表
stream_get_wrappers函数:返回注册的数据流列表
stream_register_wrapper函数:注册一个用PHP类实现的URL封装协议
stream_select函数:接收数据流数组并等待它们状态的改变
stream_set_blocking函数:将一个数据流设置为堵塞或者非堵塞状态
stream_set_timeout函数:对数据流进行超时设置
stream_set_write_buffer函数:为数据流设置缓冲区
stream_socket_accept函数:接受由函数stream_ socket_server()创建的Socket连接
stream_socket_client函数:打开网络或者UNIX主机的Socket连接
stream_socket_enable_crypto函数:为一个已经连接的Socket打开或者关闭数据加密
stream_socket_get_name函数:获取本地或者网络Socket的名称
stream_socket_pair函数:创建两个无区别的Socket数据流连接
stream_socket_recvfrom函数:从Socket获取数据,不管其连接与否
stream_socket_sendto函数:向Socket发送数据,不管其连接与否
stream_socket_server函数:创建一个网络或者UNIX Socket服务端
stream_wrapper_restore函数:恢复一个事先注销的数据包
stream_wrapper_unregister函数:注销一个URL地址包

一个过滤器的列子及解释:

官网相关链接:

用户过滤器基类:http://php.net/manual/zh/class.php-user-filter.php
过滤器注册:http://php.net/manual/zh/function.stream-filter-register.php

<?php
/* 定义一个过滤器 */
class strtoupper_filter extends php_user_filter {
 function filter($in, $out, &$consumed, $closing)
 {
  while ($bucket = stream_bucket_make_writeable($in)) { //从流里面取出一段数据
   $bucket->data = strtoupper($bucket->data);
   $consumed += $bucket->datalen;
   stream_bucket_append($out, $bucket); //将修改后的数据送到输出的地方
  }
  return PSFS_PASS_ON;
 }
}
/* 注册过滤器到php */
stream_filter_register("strtoupper", "strtoupper_filter")
  or die("Failed to register filter");
$fp = fopen("foo-bar.txt", "w");
/* 应用过滤器到一个流 */
stream_filter_append($fp, "strtoupper");
fwrite($fp, "Line1\n");
fwrite($fp, "Word - 2\n");
fwrite($fp, "Easy As 123\n");
fclose($fp);
//读取并显示内容 将全部变为大写
readfile("foo-bar.txt");
?>

希望本文所述对大家PHP程序设计有所帮助。

PHP 相关文章推荐
PHP Google的translate API代码
Dec 10 PHP
简单的PHP多图上传小程序代码
Jul 17 PHP
解析php时间戳与日期的转换
Jun 06 PHP
PHP获取windows登录用户名的方法
Jun 24 PHP
PHP调用wsdl文件类型的接口代码分享
Nov 19 PHP
PHP实现远程下载文件到本地
May 17 PHP
启用Csrf后POST数据时出现的400错误
Jul 05 PHP
ubuntu下配置nginx+php+mysql详解
Sep 10 PHP
php中引用符号(&amp;)的使用详细介绍
Dec 06 PHP
PHP中危险的file_put_contents函数详解
Nov 04 PHP
php 使用ActiveMQ发送消息,与处理消息操作示例
Feb 23 PHP
php7 新增功能实例总结
May 25 PHP
PHP实现求两个字符串最长公共子串的方法示例
Nov 17 #PHP
PHP实现求解最长公共子串问题的方法
Nov 17 #PHP
php大小写转换函数(strtolower、strtoupper)用法介绍
Nov 17 #PHP
PHP 实现人民币小写转换成大写的方法及大小写转换函数
Nov 17 #PHP
关于php支持的协议与封装协议总结(推荐)
Nov 17 #PHP
PHP实现绘制二叉树图形显示功能详解【包括二叉搜索树、平衡树及红黑树】
Nov 16 #PHP
PHP实现链式操作的三种方法详解
Nov 16 #PHP
You might like
php Memcache 中实现消息队列
2009/11/24 PHP
支持数组的ADDSLASHES的php函数
2010/02/16 PHP
PHP中输出转义JavaScript代码的实现代码
2011/04/22 PHP
Windows下部署Apache+PHP+MySQL运行环境实战
2012/08/31 PHP
php创建基本身份认证站点的方法详解
2013/06/08 PHP
仿服务器端脚本方式的JS模板实现方法
2007/04/27 Javascript
让div层随鼠标移动的实现代码 ie ff
2009/12/18 Javascript
解析Javascript小括号“()”的多义性
2013/12/03 Javascript
用JavaScript判断CSS浏览器类型前缀的两种方法
2015/10/08 Javascript
简单谈谈Javascript中类型的判断
2015/10/19 Javascript
jQuery基本选择器(实例及表单域value的获取方法)
2016/05/20 Javascript
JS常用函数和常用技巧小结
2016/10/15 Javascript
jquery实现简单的瀑布流布局
2016/12/11 Javascript
laydate.js日期时间选择插件
2017/01/04 Javascript
vue-cli项目无法用本机IP访问的解决方法
2018/09/20 Javascript
基于Vue-Cli 打包自动生成/抽离相关配置文件的实现方法
2018/12/09 Javascript
vue动态添加路由addRoutes之不能将动态路由存入缓存的解决
2019/02/19 Javascript
windows及linux环境下永久修改pip镜像源的方法
2016/11/28 Python
深入分析python中整型不会溢出问题
2018/06/18 Python
python之线程通过信号pyqtSignal刷新ui的方法
2019/01/11 Python
Python匿名函数及应用示例
2019/04/09 Python
Python分割训练集和测试集的方法示例
2019/09/19 Python
python3.7实现云之讯、聚合短信平台的短信发送功能
2019/09/26 Python
python时间与Unix时间戳相互转换方法详解
2020/02/13 Python
Python并发concurrent.futures和asyncio实例
2020/05/04 Python
英国领先的珍珠首饰品牌:Orchira
2016/09/11 全球购物
什么是ARP(Address Resolution Protocol)地址解析协议
2013/10/31 面试题
文秘自荐信
2013/10/20 职场文书
运动会入场解说词300字
2014/01/25 职场文书
监察建议书范文
2014/03/12 职场文书
化学专业自荐信
2014/05/28 职场文书
银行纠风工作实施方案
2014/06/08 职场文书
部队2014年终工作总结
2014/11/27 职场文书
2015年环境整治工作总结
2015/05/22 职场文书
python实现A*寻路算法
2021/06/13 Python
vue整合百度地图显示指定地点信息
2022/04/06 Vue.js