🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 过滤器 过滤器作为读写操作的流内容传输过程中的附加阶段. 要注意的是直到php 4.3中才加入了流过滤器, 在php 5.0对流过滤器的API设计做过较大的调整. 本章的内容遵循的是php 5的流过滤器规范. ## 在流上应用已有的过滤器 在一个打开的流上应用一个已有的过滤器只需要几行代码即可: ```c 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函数以及它的兄弟函数: ```c 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原型以及两个注册过滤器工厂的示例: ```c 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 <?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块上面: ```c #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函数如下: ```c 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使用的内存是否持久分配的: ```c 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则包含剩下的所有字符. ```c void php_stream_bucket_delref(php_stream_bucket *bucket TSRMLS_DC); void php_stream_bucket_addref(php_stream_bucket *bucket); ``` Bucket使用和zval以及资源相同的引用计数系统. 通常, 一个bucket仅属于一个上下文, 也就是它依附的队列. ```c 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) ```c void php_stream_bucket_unlink(php_stream_bucket *bucket TSRMLS_DC); ``` 在过滤器逻辑应用处理完成后, 旧的bucket必须使用这个函数从它的输入队列删除(unlink). ```c 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.md>) * 16.3 [小结](<16.3.md>)