# 16.1 上下文
# 上下文
每個流的上下文包含兩種內部消息類型. 首先最常用的是上下文選項. 這些值被安排在上下文中一個二維數組中, 通常用于改變流包裝器的初始化行為. 還有一種則是上下文參數, 它對于包裝器是未知的, 當前提供了一種方式用于在流包裝層內部的事件通知.
```
php_stream_context *php_stream_context_alloc(void);
```
通過這個API調用可以創建一個上下文, 它將分配一些存儲空間并初始化用于保存上下文選項和參數的HashTable. 還會自動的注冊為一個請求終止后將被清理的資源.
## 設置選項
設置上下文選項的內部API和用戶空間的API是等同的:
```
int php_stream_context_set_option(php_stream_context *context,
const char *wrappername, const char *optionname,
zval *optionvalue);
```
下面是用戶空間的原型:
```
bool stream_context_set_option(resource $context,
string $wrapper, string $optionname,
mixed $value);
```
它們的不同僅僅是用戶空間和內部需要的數據類型不同.下面的例子就是使用這兩個API調用, 通過內建包裝器發起一個HTTP請求, 并通過一個上下文選項覆寫了user\_agent設置.
```
php_stream *php_varstream_get_homepage(const char *alt_user_agent TSRMLS_DC)
{
php_stream_context *context;
zval tmpval;
context = php_stream_context_alloc(TSRMLS_C);
ZVAL_STRING(&tmpval, alt_user_agent, 0);
php_stream_context_set_option(context, "http", "user_agent", &tmpval);
return php_stream_open_wrapper_ex("http://www.php.net", "rb", REPORT_ERRORS | ENFORCE_SAFE_MODE, NULL, context);
}
```
> 譯者使用的php-5.4.10中php\_stream\_context\_alloc()增加了線程安全控制, 因此相應的對例子進行了修改, 請讀者測試時注意. 這里要注意的是tmpval并沒有分配任何持久性的存儲空間, 它的字符串值是通過復制設置的. php\_stream\_context\_set\_option()會自動的對傳入的zval內容進行一次拷貝.
# 取回選項
用于取回上下文選項的API調用正好是對應的設置API的鏡像:
```
int php_stream_context_get_option(php_stream_context *context,
const char *wrappername, const char *optionname,
zval ***optionvalue);
```
回顧前面, 上下文選項存儲在一個嵌套的HashTable中, 當從一個HashTable中取回值時, 一般的方法是傳遞一個指向zval \*\*的指針給zend\_hash\_find(). 當然, 由于php\_stream\_context\_get\_option()是zend\_hash\_find()的一個特殊代理, 它們的語義是相同的.
下面是內建的http包裝器使用php\_stream\_context\_get\_option()設置user\_agent的簡化版示例:
```
zval **ua_zval;
char *user_agent = "PHP/5.1.0";
if (context &&
php_stream_context_get_option(context, "http",
"user_agent", &ua_zval) == SUCCESS &&
Z_TYPE_PP(ua_zval) == IS_STRING) {
user_agent = Z_STRVAL_PP(ua_zval);
}
```
這種情況下, 非字符串值將會被丟棄, 因為對用戶代理字符串而言, 數值是沒有意義的. 其他的上下文選項, 比如max\_redirects, 則需要數字值, 由于在字符串的zval中存儲數字值并不通用, 所以需要執行一個類型轉換以使設置合法.
不幸的是這些變量是上下文擁有的, 因此它們不能直接轉換; 而需要首先進行隔離再進行轉換, 最終如果需要還要進行銷毀:
```
long max_redirects = 20;
zval **tmpzval;
if (context &&
php_stream_context_get_option(context, "http",
"max_redirects", &tmpzval) == SUCCESS) {
if (Z_TYPE_PP(tmpzval) == IS_LONG) {
max_redirects = Z_LVAL_PP(tmpzval);
} else {
zval copyval = **tmpzval;
zval_copy_ctor(©val);
convert_to_long(©val);
max_redirects = Z_LVAL(copyval);
zval_dtor(©val);
}
}
```
> 實際上, 在這個例子中, zval\_dtor()并不是必須的. IS\_LONG的變量并不需要zval容器之外的存儲空間, 因此zval\_dtor()實際上不會有真正的操作. 在這個例子中包含它是為了完整性考慮, 對于字符串, 數組, 對象, 資源以及未來可能的其他類型, 就需要這個調用了.
## 參數
雖然用戶空間API中看起來參數和上下文選項是類似的, 但實際上在語言內部的php\_stream\_context結構體中它們被定義為不同的成員.
目前只支持一個上下文參數: 通知器. php\_stream\_context結構體中的這個元素可以指向下面的php\_stream\_notifier結構體:
```
typedef struct {
php_stream_notification_func func;
void (*dtor)(php_stream_notifier *notifier);
void *ptr;
int mask;
size_t progress, progress_max;
} php_stream_notifier;
```
當將一個php*stream\_notifier結構體賦值給context->notifier時, 它將提供一個回調函數func, 在特定的流上發生下表中的PHP\_STREAM\_NOTIFY**代碼表示的事件時被觸發. 每個事件將會對應下面第二張表中的PHP*STREAM\_NOTIFY\_SEVERITY**的級別:
事件代碼含義RESOLVE主機地址解析完成. 多數基于套接字的包裝器將在連接之前執行這個查詢.CONNECT套接字流連接到遠程資源完成.AUTH\_REQUIRED請求的資源不可用, 原因是訪問控制以及缺失授權MIME\_TYPE\_IS遠程資源的mime-type不可用FILE\_SIZE\_IS遠程資源當前可用大小REDIRECTED原來的URL請求導致重定向到其他位置PROGRESS由于額外數據的傳輸導致php\_stream\_notifier結構體的progress以及(可能的)progress\_max元素被更新(進度信息, 請參考php手冊curl\_setopt的CURLOPT\_PROGRESSFUNCTION和CURLOPT\_NOPROGRESS選項)COMPLETED流上沒有更多的可用數據FAILURE請求的URL資源不成功或未完成AUTH\_RESULT遠程系統已經處理了授權認證安全碼 INFO信息更新. 等價于一個E\_NOTICE錯誤WARN小的錯誤條件. 等價于一個E\_WARNING錯誤ERR中斷錯誤條件. 等價于一個E\_ERROR錯誤.通知器實現提供了一個便利指針\*ptr用于存放額外數據. 這個指針指向的空間必須在上下文析構時被釋放, 因此必須指定一個dtor函數, 在上下文的最后一個引用離開它的作用域時調用這個dtor進行釋放. mask元素允許事件觸發限定特定的安全級別. 如果發生的事件沒有包含在mask中, 則通知器函數不會被觸發.
最后兩個元素progress和progress\_max可以由流實現設置, 然而, 通知器函數應該避免使用這兩個值, 除非它接收到PHP\_STREAM\_NOTIFY\_PROGRESS或PHP\_STREAM\_NOTIFY\_FILE\_SIZE\_IS事件通知.
下面是一個php\_stream\_notification\_func()回調原型的示例:
```
void php_sample6_notifier(php_stream_context *context,
int notifycode, int severity, char *xmsg, int xcode,
size_t bytes_sofar, size_t bytes_max,
void *ptr TSRMLS_DC)
{
if (notifycode != PHP_STREAM_NOTIFY_FAILURE) {
/* 忽略所有通知 */
return;
}
if (severity == PHP_STREAM_NOTIFY_SEVERITY_ERR) {
/* 分發到錯誤處理函數 */
php_sample6_theskyisfalling(context, xcode, xmsg);
return;
} else if (severity == PHP_STREAM_NOTIFY_SEVERITY_WARN) {
/* 日志記錄潛在問題 */
php_sample6_logstrangeevent(context, xcode, xmsg);
return;
}
}
```
## 默認上下文
在php5.0中, 當用戶空間的流創建函數被調用時, 如果沒有傳遞上下文參數, 請求一般會使用默認的上下文. 這個上下文變量存儲在文件全局結構中: FG(default\_context), 并且它可以和其他所有的php\_stream\_context變量一樣訪問. 當在用戶空間腳本執行流的創建時, 更好的方式是允許用戶指定一個上下文或者至少指定一個默認的上下文. 將用戶空間的zval \*解碼得到php\_stream\_context可以使用php\_steram\_context\_from\_zval()宏完成, 比如下面改編自第14章"訪問流"的例子:
```
PHP_FUNCTION(sample6_fopen)
{
php_stream *stream;
char *path, *mode;
int path_len, mode_len;
int options = ENFORCE_SAFE_MODE | REPORT_ERRORS;
zend_bool use_include_path = 0;
zval *zcontext = NULL;
php_stream_context *context;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
"ss|br", &path, &path_len, &mode, &mode_len,
&use_include_path, &zcontext) == FAILURE) {
return;
}
context = php_stream_context_from_zval(zcontext, 0);
if (use_include_path) {
options |= PHP_FILE_USE_INCLUDE_PATH;
}
stream = php_stream_open_wrapper_ex(path, mode, options,
NULL, context);
if (!stream) {
RETURN_FALSE;
}
php_stream_to_zval(stream, return_value);
}
```
如果zcontext包含一個用戶空間的上下文資源, 通過ZEND\_FETCH\_RESOURCE()調用獲取到它關聯的指針設置到context中. 否則, 如果zcontext為NULL并且php\_stream\_context\_from\_zval()的第二個參數設置為非0值, 這個宏則直接返回NULL. 這個例子以及幾乎所有的核心流創建的用戶空間函數中, 第二個參數都被設置為0, 此時將使用FG(default\_context)的值.
## links
- [目錄](preface.md)
- 16 [有趣的流](16.html)
- 16.2 [過濾器](16.2.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 小結