流上下文 (php_stream_context) 為流操作提供額外配置與事件通知機制。本文解析上下文選項/參數架構、php_stream_context_set_option/get_option API、HTTP 包裝器實際使用範例、通知器 (php_stream_notifier) 結構與進度回調實作。
typedef struct _php_stream_context {
HashTable *options; // 二維陣列:wrapper -> option -> zval
HashTable *params; // 參數:通知器等
// ...
} php_stream_context;
php_stream_context *context = php_stream_context_alloc(TSRMLS_C);
// 自動註冊為請求級資源,請求結束自動釋放
// 手動釋放 (罕見需求)
php_stream_context_free(context);
// 內部 API
int php_stream_context_set_option(
php_stream_context *context,
const char *wrappername, // 如 "http", "ftp", "ssl"
const char *optionname, // 如 "user_agent", "method", "header"
zval *optionvalue // 值 (字串/整數/陣列)
);
// 使用者空間對應
// stream_context_set_option($context, $wrapper, $option, $value);
php_stream *php_varstream_get_homepage(const char *alt_user_agent TSRMLS_DC) {
php_stream_context *context = php_stream_context_alloc(TSRMLS_C);
zval tmpval;
ZVAL_STRING(&tmpval, alt_user_agent, 0); // 0=不複製,臨時字串
php_stream_context_set_option(context, "http", "user_agent", &tmpval);
return php_stream_open_wrapper_ex(
"https://www.php.net", "rb",
REPORT_ERRORS | ENFORCE_SAFE_MODE,
NULL, context
);
}
注意:php_stream_context_set_option 內部會**複製** zval 內容,傳入的臨時 zval 可安全銷毀。
int php_stream_context_get_option(
php_stream_context *context,
const char *wrappername,
const char *optionname,
zval ***optionvalue // 輸出:指向 zval** 的指標
);
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);
}
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); // 釋放複製體記憶體
}
}
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_CONNECT | 連線建立 |
PHP_STREAM_NOTIFIER_AUTH_REQUIRED | 需要認證 |
PHP_STREAM_NOTIFIER_AUTH_RESULT | 認證結果 |
PHP_STREAM_NOTIFIER_MIME_TYPE_IS | MIME 型別確定 |
PHP_STREAM_NOTIFIER_PROGRESS | 傳輸進度更新 |
PHP_STREAM_NOTIFIER_COMPLETED | 傳輸完成 |
PHP_STREAM_NOTIFIER_FAILURE | 傳輸失敗 |
PHP_STREAM_NOTIFIER_REDIRECTED | 重導向發生 |
void my_notification_func(php_stream_context *context,
int notify_code, int severity,
char *xmsg, int xcode,
size_t bytes_sofar, size_t bytes_max,
void *ptr TSRMLS_DC) {
switch (notify_code) {
case PHP_STREAM_NOTIFIER_PROGRESS:
php_printf("Progress: %zu / %zu\n", bytes_sofar, bytes_max);
break;
case PHP_STREAM_NOTIFIER_COMPLETED:
php_printf("Download completed!\n");
break;
case PHP_STREAM_NOTIFIER_FAILURE:
php_printf("Download failed: %s\n", xmsg);
break;
}
}
// 在 context 中安裝通知器
php_stream_context *context = php_stream_context_alloc(TSRMLS_C);
php_stream_notifier *notifier = emalloc(sizeof(php_stream_notifier));
notifier->func = my_notification_func;
notifier->mask = PHP_STREAM_NOTIFIER_PROGRESS | PHP_STREAM_NOTIFIER_COMPLETED | PHP_STREAM_NOTIFIER_FAILURE;
context->params = emalloc(sizeof(HashTable));
zend_hash_init(context->params, 0, NULL, NULL, 0);
zend_hash_add(context->params, "notification", sizeof("notification"), ¬ifier, sizeof(notifier), NULL);
| 選項 | 型別 | 說明 |
|---|---|---|
method | String | GET/POST/PUT/DELETE 等 |
header | String/Array | 額外請求標頭 |
user_agent | String | User-Agent 標頭 |
content | String | POST/PUT 請求體 |
max_redirects | Integer | 最大重導向次數 |
timeout | Float | 連線/讀取逾時 (秒) |
follow_location | Boolean | 是否自動追蹤重導向 |
ignore_errors | Boolean | 非 2xx 狀態碼不視為錯誤 |
proxy | String | 代理伺服器 URL |
ssl | Array | SSL 選項 (verify_peer, cafile, local_cert 等) |