<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # 15.3 實現一個包裝器 # 實現一個包裝器 為了演示包裝器和流操作的內部工作原理, 我們需要重新實現php手冊的stream\_wrapper\_register()一頁示例中的var://包裝器. 此刻, 首先從下面功能完整的變量流包裝實現開始. 構建他, 并開始檢查每一塊的工作原理. > 譯注: 為了方便大家閱讀, 對代碼的注釋進行了適量補充調整, 此外, 由于phpapi的調整, 原著中的代碼不能直接在譯者使用的php-5.4.10中運行, 進行了適當的修改. 因此下面代碼結構可能和原著略有不同, 請參考閱讀.(下面opendir的例子也進行了相應的修改) ## config.m4 ``` PHP_ARG_ENABLE(varstream,whether to enable varstream support, [ enable-varstream Enable varstream support]) if test "$PHP_VARSTREAM" = "yes"; then AC_DEFINE(HAVE_VARSTREAM,1,[Whether you want varstream]) PHP_NEW_EXTENSION(varstream, varstream.c, $ext_shared) fi ``` ## php\_varstream.h ``` #ifndef PHP_VARSTREAM_H #define PHP_VARSTREAM_H extern zend_module_entry varstream_module_entry; #define phpext_varstream_ptr &varstream_module_entry #ifdef PHP_WIN32 # define PHP_VARSTREAM_API __declspec(dllexport) #elif defined(__GNUC__) && __GNUC__ >= 4 # define PHP_VARSTREAM_API __attribute__ ((visibility("default"))) #else # define PHP_VARSTREAM_API #endif #ifdef ZTS #include "TSRM.h" #endif PHP_MINIT_FUNCTION(varstream); PHP_MSHUTDOWN_FUNCTION(varstream); #define PHP_VARSTREAM_WRAPPER "var" #define PHP_VARSTREAM_STREAMTYPE "varstream" /* 變量流的抽象數據結構 */ typedef struct _php_varstream_data { off_t position; char *varname; int varname_len; } php_varstream_data; #ifdef ZTS #define VARSTREAM_G(v) TSRMG(varstream_globals_id, zend_varstream_globals *, v) #else #define VARSTREAM_G(v) (varstream_globals.v) #endif #endif ``` ## varstream.c ``` #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" #include "ext/standard/url.h" #include "php_varstream.h" static size_t php_varstream_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC) { php_varstream_data *data = stream->abstract; zval **var; size_t newlen; /* 查找變量 */ if (zend_hash_find(&EG(symbol_table), data->varname, data->varname_len + 1,(void**)&var) == FAILURE) { /* 變量不存在, 直接創建一個字符串類型的變量, 并保存新傳遞進來的內容 */ zval *newval; MAKE_STD_ZVAL(newval); ZVAL_STRINGL(newval, buf, count, 1); /* 將新的zval *放到變量中 */ zend_hash_add(&EG(symbol_table), data->varname, data->varname_len + 1, (void*)&newval, sizeof(zval*), NULL); return count; } /* 如果需要, 讓變量可寫. 這里實際上處理的是寫時復制 */ SEPARATE_ZVAL_IF_NOT_REF(var); /* 轉換為字符串類型 */ convert_to_string_ex(var); /* 重置偏移量(譯注: 相比于正常的文件系統, 這里的處理實際上不支持文件末尾的空洞創建, 讀者如果熟悉*nix文件系統, 應該了解譯者所說, 否則請略過) */ if (data->position > Z_STRLEN_PP(var)) { data->position = Z_STRLEN_PP(var); } /* 計算新的字符串長度 */ newlen = data->position + count; if (newlen < Z_STRLEN_PP(var)) { /* 總長度不變 */ newlen = Z_STRLEN_PP(var); } else if (newlen > Z_STRLEN_PP(var)) { /* 重新調整緩沖區大小以保存新內容 */ Z_STRVAL_PP(var) =erealloc(Z_STRVAL_PP(var),newlen+1); /* 更新字符串長度 */ Z_STRLEN_PP(var) = newlen; /* 確保字符串NULL終止 */ Z_STRVAL_PP(var)[newlen] = 0; } /* 將數據寫入到變量中 */ memcpy(Z_STRVAL_PP(var) + data->position, buf, count); data->position += count; return count; } static size_t php_varstream_read(php_stream *stream, char *buf, size_t count TSRMLS_DC) { php_varstream_data *data = stream->abstract; zval **var, copyval; int got_copied = 0; size_t toread = count; if (zend_hash_find(&EG(symbol_table), data->varname, data->varname_len + 1, (void**)&var) == FAILURE) { /* 變量不存在, 讀不到數據, 返回0字節長度 */ return 0; } copyval = **var; if (Z_TYPE(copyval) != IS_STRING) { /* 對于非字符串類型變量, 創建一個副本進行讀, 這樣對于只讀的變量, 就不會改變其原始類型 */ zval_copy_ctor(&copyval); INIT_PZVAL(&copyval); got_copied = 1; } if (data->position > Z_STRLEN(copyval)) { data->position = Z_STRLEN(copyval); } if ((Z_STRLEN(copyval) - data->position) < toread) { /* 防止讀取到變量可用緩沖區外的內容 */ toread = Z_STRLEN(copyval) - data->position; } /* 設置緩沖區 */ memcpy(buf, Z_STRVAL(copyval) + data->position, toread); data->position += toread; /* 如果創建了副本, 則釋放副本 */ if (got_copied) { zval_dtor(&copyval); } /* 返回設置到緩沖區的字節數 */ return toread; } static int php_varstream_closer(php_stream *stream, int close_handle TSRMLS_DC) { php_varstream_data *data = stream->abstract; /* 釋放內部結構避免泄露 */ efree(data->varname); efree(data); return 0; } static int php_varstream_flush(php_stream *stream TSRMLS_DC) { php_varstream_data *data = stream->abstract; zval **var; /* 根據不同情況, 重置偏移量 */ if (zend_hash_find(&EG(symbol_table), data->varname, data->varname_len + 1, (void**)&var) == SUCCESS) { if (Z_TYPE_PP(var) == IS_STRING) { data->position = Z_STRLEN_PP(var); } else { zval copyval = **var; zval_copy_ctor(&copyval); convert_to_string(&copyval); data->position = Z_STRLEN(copyval); zval_dtor(&copyval); } } else { data->position = 0; } return 0; } static int php_varstream_seek(php_stream *stream, off_t offset, int whence, off_t *newoffset TSRMLS_DC) { php_varstream_data *data = stream->abstract; switch (whence) { case SEEK_SET: data->position = offset; break; case SEEK_CUR: data->position += offset; break; case SEEK_END: { zval **var; size_t curlen = 0; if (zend_hash_find(&EG(symbol_table), data->varname, data->varname_len + 1, (void**)&var) == SUCCESS) { if (Z_TYPE_PP(var) == IS_STRING) { curlen = Z_STRLEN_PP(var); } else { zval copyval = **var; zval_copy_ctor(&copyval); convert_to_string(&copyval); curlen = Z_STRLEN(copyval); zval_dtor(&copyval); } } data->position = curlen + offset; break; } } /* 防止隨機訪問指針移動到緩沖區開始位置之前 */ if (data->position < 0) { data->position = 0; } if (newoffset) { *newoffset = data->position; } return 0; } static php_stream_ops php_varstream_ops = { php_varstream_write, php_varstream_read, php_varstream_closer, php_varstream_flush, PHP_VARSTREAM_STREAMTYPE, php_varstream_seek, NULL, /* cast */ NULL, /* stat */ NULL, /* set_option */ }; /* Define the wrapper operations */ static php_stream *php_varstream_opener( php_stream_wrapper *wrapper, char *filename, char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC) { php_varstream_data *data; php_url *url; if (options & STREAM_OPEN_PERSISTENT) { /* 按照變量流的定義, 是不能持久化的 * 因為變量在請求結束后將被釋放 */ php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to open %s persistently", filename); return NULL; } /* 標準URL解析: scheme://user:pass@host:port/path?query#fragment */ url = php_url_parse(filename); if (!url) { php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unexpected error parsing URL"); return NULL; } /* 檢查是否有變量流URL必須的元素host, 以及scheme是否是var */ if (!url->host || (url->host[0] == 0) || strcasecmp("var", url->scheme) != 0) { /* Bad URL or wrong wrapper */ php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Invalid URL, must be in the form: " "var://variablename"); php_url_free(url); return NULL; } /* 創建一個數據結構保存協議信息(變量流協議重要是變量名, 變量名長度, 當前偏移量) */ data = emalloc(sizeof(php_varstream_data)); data->position = 0; data->varname_len = strlen(url->host); data->varname = estrndup(url->host, data->varname_len + 1); /* 釋放前面解析出來的url占用的內存 */ php_url_free(url); /* 實例化一個流, 為其賦予恰當的流ops, 綁定抽象數據 */ return php_stream_alloc(&php_varstream_ops, data, 0, mode); } static php_stream_wrapper_ops php_varstream_wrapper_ops = { php_varstream_opener, /* 調用php_stream_open_wrapper(sprintf("%s://xxx", PHP_VARSTREAM_WRAPPER))時執行 */ NULL, /* stream_close */ NULL, /* stream_stat */ NULL, /* url_stat */ NULL, /* dir_opener */ PHP_VARSTREAM_WRAPPER, NULL, /* unlink */ #if PHP_MAJOR_VERSION >= 5 /* PHP >= 5.0 only */ NULL, /* rename */ NULL, /* mkdir */ NULL, /* rmdir */ #endif }; static php_stream_wrapper php_varstream_wrapper = { &php_varstream_wrapper_ops, NULL, /* abstract */ 0, /* is_url */ }; PHP_MINIT_FUNCTION(varstream) { /* 注冊流包裝器: * 1. 檢查流包裝器名字是否正確(符合這個正則: /^[a-zA-Z0-9+.-]+$/) * 2. 將傳入的php_varstream_wrapper增加到url_stream_wrappers_hash這個HashTable中, key為PHP_VARSTREAM_WRAPPER */ if (php_register_url_stream_wrapper(PHP_VARSTREAM_WRAPPER, &php_varstream_wrapper TSRMLS_CC)==FAILURE) { return FAILURE; } return SUCCESS; } PHP_MSHUTDOWN_FUNCTION(varstream) { /* 卸載流包裝器: 從url_stream_wrappers_hash中刪除 */ if (php_unregister_url_stream_wrapper(PHP_VARSTREAM_WRAPPER TSRMLS_CC) == FAILURE) { return FAILURE; } return SUCCESS; } zend_module_entry varstream_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif "varstream", NULL, PHP_MINIT(varstream), PHP_MSHUTDOWN(varstream), NULL, NULL, NULL, #if ZEND_MODULE_API_NO >= 20010901 "0.1", #endif STANDARD_MODULE_PROPERTIES }; #ifdef COMPILE_DL_VARSTREAM ZEND_GET_MODULE(varstream) #endif ``` 在構建加載擴展后, php就可以處理以var://開始的URL的請求, 它的行為和手冊中用戶空間實現的行為一致. ## 內部實現 首先你注意到的可能是這個擴展完全沒有暴露用戶空間函數. 它所做的只是在MINIT函數中調用了一個核心PHPAPI的鉤子, 將var協議和我們定義的包裝器關聯起來: ``` static php_stream_wrapper php_varstream_wrapper = { &php_varstream_wrapper_ops, NULL, /* abstract */ 0, /* is_url */ } ``` 很明顯, 最重要的元素就是ops, 它提供了訪問特定流包裝器的創建以及檢查函數. 你可以安全的忽略abstract屬性, 它僅在運行時使用, 在初始化定義時, 它只是作為一個占位符. 第三個元素is\_url, 它告訴php在使用這個包裝器時是否考慮php.ini中的allow\_url\_fopen選項. 如果這個值非0, 并且將allow\_url\_fopen設置為false, 則這個包裝器不能被腳本使用. 在本章前面你已經知道, 調用用戶空間函數比如fopen將通過這個包裝器的ops元素得到php\_varstream\_wrapper\_ops, 這樣去調用流的打開函數php\_varstream\_opener. 這個函數的第一塊代碼檢查是否請求持久化的流: ``` if (options & STREAM_OPEN_PERSISTENT) { ``` 對于很多包裝器這樣的請求是合法的. 然而目前的情況這個行為沒有意義. 一方面用戶空間變量的定義就是臨時的, 另一方面, varstream的實例化代價很低, 這就使得持久化的優勢很小. 像流包裝層報告錯誤很簡單, 只需要返回一個NULL值而不是流實例即可. 流包裝層透出到用戶空間的失敗消息并不會說明具體的錯誤, 只是說明不能打開URL. 要想給開發者暴露更多的錯誤信息, 可以在返回之前使用php\_stream\_wrapper\_log\_error()函數. ``` php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to open %s persistently", filename); return NULL; ``` ## URL解析 實例化varstream的下一步需要一個人類可讀的URL, 將它分塊放入到一個易管理的結構體中. 幸運的是它使用了和用戶空間url\_parse()函數相同的機制. 如果URL成功解析, 將會分配一個php\_url結構體并設置合適的值. 如果在URL中沒有某些值, 在返回的php\_url中對應的將被設置為NULL. 這個結構體必須在離開php\_varstream\_opener函數之前被顯式釋放, 否則它的內存將會泄露: ``` typedef struct php_url { /* scheme://user:pass@host:port/path?query#fragment */ char *scheme; char *user; char *pass; char *host; unsigned short port; char *path; char *query; char *fragment; } php_url; ``` 最后, varstream包裝器創建了一個數據結構, 保存了流指向的變量名, 讀取時的當前位置. 這個結構體將在流的讀取和寫入函數中用于獲取變量, 并且將在流結束使用時由php\_varstream\_close函數釋放. ## opendir() 讀寫變量內容的實現可以再次進行擴展. 這里可以加入一個新的特性, 允許使用目錄函數讀取數組中的key. 在你的php\_varstream\_wrapper\_ops結構體之前增加下面的代碼: ``` static size_t php_varstream_readdir(php_stream *stream, char *buf, size_t count TSRMLS_DC) { php_stream_dirent *ent = (php_stream_dirent*)buf; php_varstream_dirdata *data = stream->abstract; char *key; int type, key_len; long idx; /* 查找數組中的key */ type = zend_hash_get_current_key_ex(Z_ARRVAL_P(data->arr), &key, &key_len, &idx, 0, &(data->pos)); /* 字符串key */ if (type == HASH_KEY_IS_STRING) { if (key_len >= sizeof(ent->d_name)) { /* truncate long keys to maximum length */ key_len = sizeof(ent->d_name) - 1; } /* 設置到目錄結構上 */ memcpy(ent->d_name, key, key_len); ent->d_name[key_len] = 0; /* 數值key */ } else if (type == HASH_KEY_IS_LONG) { /* 設置到目錄結構上 */ snprintf(ent->d_name, sizeof(ent->d_name), "%ld",idx); } else { /* 迭代結束 */ return 0; } /* 移動數組指針(位置記錄到流的抽象結構中) */ zend_hash_move_forward_ex(Z_ARRVAL_P(data->arr), &data->pos); return sizeof(php_stream_dirent); } static int php_varstream_closedir(php_stream *stream, int close_handle TSRMLS_DC) { php_varstream_dirdata *data = stream->abstract; zval_ptr_dtor(&(data->arr)); efree(data); return 0; } static int php_varstream_dirseek(php_stream *stream, off_t offset, int whence, off_t *newoffset TSRMLS_DC) { php_varstream_dirdata *data = stream->abstract; if (whence == SEEK_SET && offset == 0) { /* 重置數組指針 */ zend_hash_internal_pointer_reset_ex( Z_ARRVAL_P(data->arr), &(data->pos)); if (newoffset) { *newoffset = 0; } return 0; } /* 不支持其他類型的隨機訪問 */ return -1; } static php_stream_ops php_varstream_dirops = { NULL, /* write */ php_varstream_readdir, php_varstream_closedir, NULL, /* flush */ PHP_VARSTREAM_DIRSTREAMTYPE, php_varstream_dirseek, NULL, /* cast */ NULL, /* stat */ NULL, /* set_option */ }; static php_stream *php_varstream_opendir( php_stream_wrapper *wrapper, char *filename, char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC) { php_varstream_dirdata *data; php_url *url; zval **var; /* 不支持持久化流 */ if (options & STREAM_OPEN_PERSISTENT) { php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to open %s persistently", filename); return NULL; } /* 解析URL */ url = php_url_parse(filename); if (!url) { php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unexpected error parsing URL"); return NULL; } /* 檢查請求URL的正確性 */ if (!url->host || (url->host[0] == 0) || strcasecmp("var", url->scheme) != 0) { /* Bad URL or wrong wrapper */ php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Invalid URL, must be in the form: " "var://variablename"); php_url_free(url); return NULL; } /* 查找變量 */ if (zend_hash_find(&EG(symbol_table), url->host, strlen(url->host) + 1, (void**)&var) == FAILURE) { php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Variable $%s not found", url->host); php_url_free(url); return NULL; } /* 檢查變量類型 */ if (Z_TYPE_PP(var) != IS_ARRAY) { php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "$%s is not an array", url->host); php_url_free(url); return NULL; } /* 釋放前面分配的URL結構 */ php_url_free(url); /* 分配抽象數據結構 */ data = emalloc(sizeof(php_varstream_dirdata)); if ( Z_ISREF_PP(var) && Z_REFCOUNT_PP(var) > 1) { /* 全拷貝 */ MAKE_STD_ZVAL(data->arr); *(data->arr) = **var; zval_copy_ctor(data->arr); INIT_PZVAL(data->arr); } else { /* 寫時拷貝 */ data->arr = *var; Z_SET_REFCOUNT_P(data->arr, Z_REFCOUNT_P(data->arr) + 1); } /* 重置數組指針 */ zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(data->arr), &data->pos); return php_stream_alloc(&php_varstream_dirops,data,0,mode); } ``` 現在, 將你的php\_varstream\_wrapper\_ops結構體中的dir\_opener的NULL替換成你的php\_varstream\_opendir函數. 最后, 將下面新定義的類型放入到你的php\_varstream.h文件的php\_varstream\_data定義下面: ``` #define PHP_VARSTREAM_DIRSTREAMTYPE "varstream directory" typedef struct _php_varstream_dirdata { zval *arr; HashPosition pos; } php_varstream_dirdata; ``` 在你基于fopen()實現的varstream包裝器中, 你直接使用持久變量名, 每次執行讀寫操作時從符號表中獲取變量. 而這里, opendir()的實現中獲取變量時處理了變量不存在或者類型錯誤的異常. 你還有一個數組變量的拷貝, 這就說明原數組的改變并不會影響后續的readdir()調用的結果. 原來存儲變量名的方式也可以正常工作, 這里只是給出另外一種選擇作為演示示例. 由于目錄訪問是基于成塊的目錄條目, 而不是字符, 因此這里需要一套獨立的流操作. 這個版本中, write沒有意義, 因此保持它為NULL. read的實現使用zend\_hash\_get\_current\_key\_ex()函數將數組映射到目錄名. 而隨機訪問也只是對SEEK\_SET有效, 用來響應rewinddir()跳轉到數組開始位置. > 實際上, 目錄流并沒有使用SEEK\_CUR, SEEK\_END, 或者除了0之外的偏移量. 在實現目錄流操作時, 最好還是涉及你的函數能以某種方式處理這些情況, 以使得在流包裝層變化時能夠適應其目錄隨機訪問. ## links - [目錄](preface.md) - 15.2 [包裝器操作](15.2.html) - 15.4 [操縱](15.4.html)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看