# 16.2 过滤器
# 过滤器
过滤器作为读写操作的流内容传输过程中的附加阶段. 要注意的是直到php 4.3中才加入了流过滤器, 在php 5.0对流过滤器的API设计做过较大的调整. 本章的内容遵循的是php 5的流过滤器规范.
## 在流上应用已有的过滤器
在一个打开的流上应用一个已有的过滤器只需要几行代码即可:
```
php_stream *php_sample6_fopen_read_ucase(const char *path
TSRMLS_DC) {
php_stream_filter *filter;
php_stream *stream;
stream = php_stream_open_wrapper_ex(path, "r",
REPORT_ERRORS | ENFORCE_SAFE_MODE,
NULL, FG(default_context));
if (!stream) {
return NULL;
}
filter = php_stream_filter_create("string.toupper", NULL,
0 TSRMLS_CC);
if (!filter) {
php_stream_close(stream);
return NULL;
}
php_stream_filter_append(&stream->readfilters, filter);
return stream;
}
```
首先来看看这里引入的API函数以及它的兄弟函数:
```
php_stream_filter *php_stream_filter_create(
const char *filtername, zval *filterparams,
int persistent TSRMLS_DC);
void php_stream_filter_prepend(php_stream_filter_chain *chain,
php_stream_filter *filter);
void php_stream_filter_append(php_stream_filter_chain *chain,
php_stream_filter *filter);
```
php\_stream\_filter\_create()的filterparams参数和用户空间对应的stream\_filter\_append()和stream\_filter\_prepend()函数的同名参数含义一致. 要注意, 所有传递到php\_stream\_filter\_create()的zval \*数据都不是过滤器所拥有的. 它们只是在过滤器创建期间被借用而已, 因此在调用作用域分配传入的所有内存空间都要手动释放.
如果过滤器要被应用到一个持久化流, 则必须设置persistent参数为非0值. 如果你不确认你要应用过滤器的流是否持久化的, 则可以使用php\_stream\_is\_persistent()宏进行检查, 它只接受一个php\_stream \*类型的参数.
如在前面例子中看到的, 流过滤器被隔离到两个独立的链条中. 一个用于写操作中对php\_stream\_write()调用响应时的stream->ops->write()调用之前. 另外一个用于读操作中对stream->ops->read()取回的所有数据进行处理.
在这个例子中你使用&stream->readfilters指示读的链条. 如果你想要在写的链条上应用一个过滤器, 则可以使用&stream->writefilters.
## 定义一个过滤器实现
注册过滤器实现和注册包装器遵循相同的基础规则. 第一步是在MINIT阶段向php中引入你的过滤器, 与之匹配的是在MSHUTDOWN阶段移除它. 下面是需要调用的API原型以及两个注册过滤器工厂的示例:
```
int php_stream_filter_register_factory(
const char *filterpattern,
php_stream_filter_factory *factory TSRMLS_DC);
int php_stream_filter_unregister_factory(
const char *filterpattern TSRMLS_DC);
PHP_MINIT_FUNCTION(sample6)
{
php_stream_filter_register_factory("sample6",
&php_sample6_sample6_factory TSRMLS_CC);
php_stream_filter_register_factory("sample.*",
&php_sample6_samples_factory TSRMLS_CC);
return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(sample6)
{
php_stream_filter_unregister_factory("sample6" TSRMLS_CC);
php_stream_filter_unregister_factory("sample.*"
TSRMLS_CC);
return SUCCESS;
}
```
这里注册的第一个工厂定义了一个具体的过滤器名sample6; 第二个则利用了流包装层内部的基本匹配规则. 为了进行演示, 下面的用户空间代码, 每行都将尝试通过不同的名字实例化php\_sample6\_samples\_factory.
```
<?php
stream_filter_append(STDERR, 'sample.one');
stream_filter_append(STDERR, 'sample.3');
stream_filter_append(STDERR, 'sample.filter.thingymabob');
stream_filter_append(STDERR, 'sample.whatever');
?>
```
php\_sample6\_samples\_factory的定义如下面代码, 你可以将这些代码放到你的MINIT块上面:
```
#include "ext/standard/php_string.h"
typedef struct {
char is_persistent;
char *tr_from;
char *tr_to;
int tr_len;
} php_sample6_filter_data;
/* 过滤逻辑 */
static php_stream_filter_status_t php_sample6_filter(
php_stream *stream, php_stream_filter *thisfilter,
php_stream_bucket_brigade *buckets_in,
php_stream_bucket_brigade *buckets_out,
size_t *bytes_consumed, int flags TSRMLS_DC)
{
php_stream_bucket *bucket;
php_sample6_filter_data *data = thisfilter->abstract;
size_t consumed = 0;
while ( buckets_in->head ) {
bucket = php_stream_bucket_make_writeable(buckets_in->head TSRMLS_CC);
php_strtr(bucket->buf, bucket->buflen, data->tr_from, data->tr_to, data->tr_len);
consumed += bucket->buflen;
php_stream_bucket_append(buckets_out, bucket TSRMLS_CC);
}
if ( bytes_consumed ) {
*bytes_consumed = consumed;
}
return PSFS_PASS_ON;
}
/* 过滤器的释放 */
static void php_sample6_filter_dtor(php_stream_filter *thisfilter TSRMLS_DC)
{
php_sample6_filter_data *data = thisfilter->abstract;
pefree(data, data->is_persistent);
}
/* 流过滤器操作表 */
static php_stream_filter_ops php_sample6_filter_ops = {
php_sample6_filter,
php_sample6_filter_dtor,
"sample.*",
};
/* 字符翻译使用的表 */
#define PHP_SAMPLE6_ALPHA_UCASE "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
#define PHP_SAMPLE6_ALPHA_LCASE "abcdefghijklmnopqrstuvwxyz"
#define PHP_SAMPLE6_ROT13_UCASE "NOPQRSTUVWXYZABCDEFGHIJKLM"
#define PHP_SAMPLE6_ROT13_LCASE "nopqrstuvwxyzabcdefghijklm"
/* 创建流过滤器实例的过程 */
static php_stream_filter *php_sample6_filter_create(
const char *name, zval *param, int persistent TSRMLS_DC)
{
php_sample6_filter_data *data;
char *subname;
/* 安全性检查 */
if ( strlen(name) < sizeof("sample.") || strncmp(name, "sample.", sizeof("sample.") - 1) ) {
return NULL;
}
/* 分配流过滤器数据 */
data = pemalloc(sizeof(php_sample6_filter_data), persistent);
if ( !data ) {
return NULL;
}
/* 设置持久性 */
data->is_persistent = persistent;
/* 根据调用时的名字, 对过滤器数据进行适当初始化 */
subname = (char *)name + sizeof("sample.") - 1;
if ( strcmp(subname, "ucase") == 0 ) {
data->tr_from = PHP_SAMPLE6_ALPHA_LCASE;
data->tr_to = PHP_SAMPLE6_ALPHA_UCASE;
} else if ( strcmp(subname, "lcase") == 0 ) {
data->tr_from = PHP_SAMPLE6_ALPHA_UCASE;
data->tr_to = PHP_SAMPLE6_ALPHA_LCASE;
} else if ( strcmp(subname, "rot13") == 0 ) {
data->tr_from = PHP_SAMPLE6_ALPHA_LCASE
PHP_SAMPLE6_ALPHA_UCASE;;
data->tr_to = PHP_SAMPLE6_ROT13_LCASE
PHP_SAMPLE6_ROT13_UCASE;
} else {
/* 不支持 */
pefree(data, persistent);
return NULL;
}
/* 节省未来使用时每次的计算 */
data->tr_len = strlen(data->tr_from);
/* 分配一个php_stream_filter结构并按指定参数初始化 */
return php_stream_filter_alloc(&php_sample6_filter_ops, data, persistent);
}
/* 流过滤器工厂, 用于创建流过滤器实例(php_stream_filter_append/prepend的时候) */
static php_stream_filter_factory php_sample6_samples_factory = {
php_sample6_filter_create
};
```
> 译注: 下面是译者对整个流程的分析
>
> 一. MINIT阶段的register操作将在stream\_filters\_hash这个HashTable中注册一个php\_stream\_filter\_factory结构, 它只有一个成员create\_filter, 用来创建过滤器实例.
>
> 二. 用户空间代码stream\_filter\_append(STDERR, 'sapmple.one');在内部的实现是apply\_filter\_to\_stream()函数(ext/standard/streamsfuncs.c中), 这里有两步操作, 首先创建过滤器, 然后将过滤器按照参数追加到流的readfilters/writefilters相应链中;
>
> 二.一 创建过滤器(php\_stream\_filter\_create()): 首先直接按照传入的名字精确的从stream\_filters\_hash(或FG(stream\_filters))中查找, 如果没有, 从右向左替换句点后面的内容为星号"\*"进行查找, 直到找到注册的过滤器工厂或错误返回. 一旦找到注册的过滤器工厂, 就调用它的create\_filter成员, 创建流过滤器实例.
>
> 二.二 直接按照参数描述放入流的readfilters/writefilters相应位置.
>
> 三. 用户向该流进行写入或读取操作时(以写为例): 此时内部将调用\_php\_stream\_write(), 在这个函数中, 如果流的writefilters非空, 则调用流过滤器的fops->filter()执行过滤, 并根据返回状态做相应处理.
>
> 四. 当流的生命周期结束, 流被释放的时候, 将会检查流的readfilters/writefilters是否为空, 如果非空, 相应的调用php\_stream\_filter\_remove()进行释放, 其中就调用了fops->fdtor对流过滤器进行释放.
上一章我们已经熟悉了流包装器的实现, 你可能能够识别这里的基本结构. 工厂函数(php\_sample6\_samples\_filter\_create)被调用分配一个过滤器实例, 并赋值给一个操作集合和抽象数据. 这上面的例子中, 你的工厂为所有的过滤器类型赋值了相同的ops结构, 但使用了不同的初始化数据.
调用作用域将得到这里分配的过滤器, 并将它赋值给流的readfilters链或writefilters链. 接着, 当流的读/写操作被调用时, 过滤器链将数据放入到一个或多个php\_stream\_bucket结构体, 并将这些bucket组织到一个队列php\_stream\_bucket\_brigade中传递给过滤器.
这里, 你的过滤器实现是前面的php\_sample6\_filter, 它取出输入队列bucket中的数据, 使用php\_sample6\_filter\_create中确定的字符表执行字符串翻译, 并将修改后的bucket放入到输出队列.
由于这个过滤器的实现并没有其他内部缓冲, 因此几乎不可能出错, 因此它总是返回PSFS\_PASS\_ON, 告诉流包装层有数据被过滤器存放到了输出队列中. 如果过滤器执行了内部缓冲消耗了所有的输入数据而没有产生输出, 就需要返回PSFS\_FEED\_ME标识过滤器循环周期在没有其他输入数据时暂时停止. 如果过滤器碰到了关键性的错误, 它应该返回PSFS\_ERR\_FATAL, 它将指示流包装层, 过滤器链处于不稳定状态. 这将导致流被关闭.
用于维护bucket和bucket队列的API函数如下:
```
php_stream_bucket *php_stream_bucket_new(php_stream *stream,
char *buf, size_t buflen, int own_buf,
int buf_persistent TSRMLS_DC);
```
创建一个php\_stream\_bucket用于存放到输出队列. 如果own\_buf被设置为非0值, 流包装层可以并且通常都会修改它的内容或在某些点释放分配的内存. buf\_persistent的非0值标识buf使用的内存是否持久分配的:
```
int php_stream_bucket_split(php_stream_bucket *in,
php_stream_bucket **left, php_stream_bucket **right,
size_t length TSRMLS_DC);
```
这个函数将in这个bucket的内容分离到两个独立的bucket对象中. left这个bucket将包含in中的前length个字符, 而right则包含剩下的所有字符.
```
void php_stream_bucket_delref(php_stream_bucket *bucket
TSRMLS_DC);
void php_stream_bucket_addref(php_stream_bucket *bucket);
```
Bucket使用和zval以及资源相同的引用计数系统. 通常, 一个bucket仅属于一个上下文, 也就是它依附的队列.
```
void php_stream_bucket_prepend(
php_stream_bucket_brigade *brigade,
php_stream_bucket *bucket TSRMLS_DC);
void php_stream_bucket_append(
php_stream_bucket_brigade *brigade,
php_stream_bucket *bucket TSRMLS_DC);
```
这两个函数扮演了过滤器子系统的苦力, 用于附加bucket到队列的开始(prepend)或末尾(append)
```
void php_stream_bucket_unlink(php_stream_bucket *bucket
TSRMLS_DC);
```
在过滤器逻辑应用处理完成后, 旧的bucket必须使用这个函数从它的输入队列删除(unlink).
```
php_stream_bucket *php_stream_bucket_make_writeable(
php_stream_bucket *bucket TSRMLS_DC);
```
将一个bucket从它所依附的队列中移除, 并且如果需要, 赋值bucket->buf的内部缓冲区, 这样就使得它的内容可修改. 在某些情况下, 比如当输入bucket的引用计数大于1时, 返回的bucket将会是不同的实例, 而不是传入的实例. 因此, 我们要保证在调用作用域使用的是返回的bucket, 而不是传入的bucket.
## links
- [目录](preface.md)
- 16.1 [上下文](16.1.html)
- 16.3 [小结](16.3.html)
- 介绍
- 1 PHP的生命周期
- 1.1 让我们从SAPI开始
- 1.2 PHP的启动与终止
- 1.3 PHP的生命周期
- 1.4 线程安全
- 1.5 PHP的生命周期
- 2 PHP变量在内核中的实现
- 2.1 变量的类型
- 2.2 变量的值
- 2.3 创建PHP变量
- 2.4 变量的存储方式
- 2.5 变量的检索
- 2.6 类型转换
- 2.7 小结
- 3 内存管理
- 3.1 内存管理
- 3.2 引用计数
- 3.3 内存管理
- 4 动手编译PHP
- 4.1 动手编译PHP
- 4.2 动手编译PHP
- 4.3 Unix/Linux平台下的编译
- 4.4 在Win32平台上编译PHP
- 4.5 动手编译PHP
- 5 Your First Extension
- 5.1 Your First Extension
- 5.2 编译我们的扩展
- 5.3 静态编译
- 5.4 编写函数
- 5.5 Your First Extension
- 6 函数返回值
- 6.1 函数返回值
- 6.2 引用与函数的执行结果
- 6.3 函数返回值
- 7 函数的参数
- 7.1 函数的参数
- 7.2 函数的参数
- 7.3 函数的参数
- 8 使用HashTable与{数组}
- 8.1 使用HashTable与{数组}
- 8.2 使用HashTable与{数组}
- 8.3 使用HashTable与{数组}
- 8.4 使用HashTable与{数组}
- 9 PHP中的资源类型
- 9.1 PHP中的资源类型
- 9.2 PHP中的资源类型
- 9.3 PHP中的资源类型
- 9.4 PHP中的资源类型
- 10 PHP中的面向对象(一)
- 10.1 PHP中的面向对象(一)
- 10.2 PHP中的面向对象(一)
- 10.3 PHP中的面向对象(一)
- 10.4 PHP中的面向对象(一)
- 10.5 PHP中的面向对象(一)
- 11 PHP中的面向对象(二)
- 11.1 PHP中的面向对象(二)
- 11.2 PHP中的面向对象(二)
- 11.3 PHP中的面向对象(二)
- 12 启动与终止的那点事
- 12.1 关于生命周期
- 12.2 MINFO与phpinfo
- 12.3 常量
- 12.4 PHP扩展中的全局变量
- 12.5 PHP语言中的超级全局变量(Superglobals)
- 12.6 小结
- 13 INI设置
- 13.1 声明和访问INI设置
- 13.2 小结
- 14 流式访问
- 14.1 流的概览
- 14.2 访问流
- 14.3 静态资源操作
- 14.4 links
- 15 流的实现
- 15.1 php流的表象之下
- 15.2 包装器操作
- 15.3 实现一个包装器
- 15.4 操纵
- 15.5 检查
- 15.6 小结
- 16 有趣的流
- 16.1 上下文
- 16.2 过滤器
- 16.3 小结
- 17 配置和链接
- 17.1 autoconf
- 17.2 库的查找
- 17.3 强制模块依赖
- 17.4 Windows方言
- 17.5 小结
- 18 扩展生成
- 18.1 ext_skel
- 18.2 PECL_Gen
- 18.3 小结
- 19 设置宿主环境
- 19.1 嵌入式SAPI
- 19.2 构建并编译一个宿主应用
- 19.3 通过嵌入包装重新创建cli
- 19.4 老技术新用
- 19.5 小结
- 20 高级嵌入式
- 20.1 回调到php中
- 20.2 错误处理
- 20.3 初始化php
- 20.4 覆写INI_SYSTEM和INI_PERDIR选项
- 20.5 捕获输出
- 20.6 同时扩展和嵌入
- 20.7 小结