# 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 小結