有趣的流
php常被提起的一個特性是流上下文. 這個可選的參數甚至在用戶空間大多數流創建相關的函數中都可用, 它作為一個泛化的框架用於向給定包裝器或流實現傳入/傳出額外的信息.
上下文
每個流的上下文包含兩種內部消息類型. 首先最常用的是上下文選項. 這些值被安排在上下文中一個二維數組中, 通常用於改變流包裝器的初始化行為. 還有一種則是上下文參數, 它對於包裝器是未知的, 當前提供了一種方式用於在流包裝層內部的事件通知.
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;