实现一个包装器

    此刻, 首先从下面功能完整的变量流包装实现开始. 构建他, 并开始检查每一块的工作原理.

    php_varstream.h

    1. #define PHP_VARSTREAM_H
    2. extern zend_module_entry varstream_module_entry;
    3. #define phpext_varstream_ptr &varstream_module_entry
    4. #ifdef PHP_WIN32
    5. # define PHP_VARSTREAM_API __declspec(dllexport)
    6. #elif defined(__GNUC__) && __GNUC__ >= 4
    7. # define PHP_VARSTREAM_API __attribute__ ((visibility("default")))
    8. #else
    9. # define PHP_VARSTREAM_API
    10. #endif
    11. #ifdef ZTS
    12. #include "TSRM.h"
    13. #endif
    14. PHP_MINIT_FUNCTION(varstream);
    15. PHP_MSHUTDOWN_FUNCTION(varstream);
    16. #define PHP_VARSTREAM_WRAPPER "var"
    17. #define PHP_VARSTREAM_STREAMTYPE "varstream"
    18. /* 变量流的抽象数据结构 */
    19. typedef struct _php_varstream_data {
    20. off_t position;
    21. char *varname;
    22. int varname_len;
    23. } php_varstream_data;
    24. #ifdef ZTS
    25. #define VARSTREAM_G(v) TSRMG(varstream_globals_id, zend_varstream_globals *, v)
    26. #else
    27. #define VARSTREAM_G(v) (varstream_globals.v)
    28. #endif
    29. #endif
    1. #ifdef HAVE_CONFIG_H
    2. #include "config.h"
    3. #endif
    4. #include "php.h"
    5. #include "php_ini.h"
    6. #include "ext/standard/info.h"
    7. #include "ext/standard/url.h"
    8. #include "php_varstream.h"
    9. static size_t php_varstream_write(php_stream *stream,
    10. const char *buf, size_t count TSRMLS_DC)
    11. {
    12. php_varstream_data *data = stream->abstract;
    13. zval **var;
    14. size_t newlen;
    15. /* 查找变量 */
    16. if (zend_hash_find(&EG(symbol_table), data->varname,
    17. data->varname_len + 1,(void**)&var) == FAILURE) {
    18. /* 变量不存在, 直接创建一个字符串类型的变量, 并保存新传递进来的内容 */
    19. zval *newval;
    20. MAKE_STD_ZVAL(newval);
    21. ZVAL_STRINGL(newval, buf, count, 1);
    22. /* 将新的zval *放到变量中 */
    23. zend_hash_add(&EG(symbol_table), data->varname,
    24. data->varname_len + 1, (void*)&newval,
    25. sizeof(zval*), NULL);
    26. return count;
    27. }
    28. /* 如果需要, 让变量可写. 这里实际上处理的是写时复制 */
    29. SEPARATE_ZVAL_IF_NOT_REF(var);
    30. /* 转换为字符串类型 */
    31. convert_to_string_ex(var);
    32. /* 重置偏移量(译注: 相比于正常的文件系统, 这里的处理实际上不支持文件末尾的空洞创建, 读者如果熟悉*nix文件系统, 应该了解译者所说, 否则请略过) */
    33. if (data->position > Z_STRLEN_PP(var)) {
    34. data->position = Z_STRLEN_PP(var);
    35. }
    36. /* 计算新的字符串长度 */
    37. newlen = data->position + count;
    38. if (newlen < Z_STRLEN_PP(var)) {
    39. /* 总长度不变 */
    40. newlen = Z_STRLEN_PP(var);
    41. } else if (newlen > Z_STRLEN_PP(var)) {
    42. /* 重新调整缓冲区大小以保存新内容 */
    43. Z_STRVAL_PP(var) =erealloc(Z_STRVAL_PP(var),newlen+1);
    44. /* 更新字符串长度 */
    45. Z_STRLEN_PP(var) = newlen;
    46. /* 确保字符串NULL终止 */
    47. Z_STRVAL_PP(var)[newlen] = 0;
    48. }
    49. /* 将数据写入到变量中 */
    50. memcpy(Z_STRVAL_PP(var) + data->position, buf, count);
    51. data->position += count;
    52. return count;
    53. }
    54. static size_t php_varstream_read(php_stream *stream,
    55. char *buf, size_t count TSRMLS_DC)
    56. {
    57. php_varstream_data *data = stream->abstract;
    58. zval **var, copyval;
    59. int got_copied = 0;
    60. size_t toread = count;
    61. if (zend_hash_find(&EG(symbol_table), data->varname,
    62. data->varname_len + 1, (void**)&var) == FAILURE) {
    63. /* 变量不存在, 读不到数据, 返回0字节长度 */
    64. return 0;
    65. }
    66. copyval = **var;
    67. if (Z_TYPE(copyval) != IS_STRING) {
    68. /* 对于非字符串类型变量, 创建一个副本进行读, 这样对于只读的变量, 就不会改变其原始类型 */
    69. zval_copy_ctor(&copyval);
    70. INIT_PZVAL(&copyval);
    71. got_copied = 1;
    72. }
    73. if (data->position > Z_STRLEN(copyval)) {
    74. data->position = Z_STRLEN(copyval);
    75. }
    76. if ((Z_STRLEN(copyval) - data->position) < toread) {
    77. /* 防止读取到变量可用缓冲区外的内容 */
    78. toread = Z_STRLEN(copyval) - data->position;
    79. }
    80. /* 设置缓冲区 */
    81. memcpy(buf, Z_STRVAL(copyval) + data->position, toread);
    82. data->position += toread;
    83. /* 如果创建了副本, 则释放副本 */
    84. if (got_copied) {
    85. zval_dtor(&copyval);
    86. }
    87. /* 返回设置到缓冲区的字节数 */
    88. return toread;
    89. }
    90. static int php_varstream_closer(php_stream *stream,
    91. int close_handle TSRMLS_DC)
    92. {
    93. php_varstream_data *data = stream->abstract;
    94. /* 释放内部结构避免泄露 */
    95. efree(data->varname);
    96. efree(data);
    97. return 0;
    98. }
    99. static int php_varstream_flush(php_stream *stream TSRMLS_DC)
    100. {
    101. php_varstream_data *data = stream->abstract;
    102. zval **var;
    103. /* 根据不同情况, 重置偏移量 */
    104. if (zend_hash_find(&EG(symbol_table), data->varname,
    105. data->varname_len + 1, (void**)&var)
    106. == SUCCESS) {
    107. if (Z_TYPE_PP(var) == IS_STRING) {
    108. data->position = Z_STRLEN_PP(var);
    109. } else {
    110. zval copyval = **var;
    111. zval_copy_ctor(&copyval);
    112. convert_to_string(&copyval);
    113. data->position = Z_STRLEN(copyval);
    114. zval_dtor(&copyval);
    115. } else {
    116. data->position = 0;
    117. }
    118. return 0;
    119. }
    120. static int php_varstream_seek(php_stream *stream, off_t offset,
    121. int whence, off_t *newoffset TSRMLS_DC)
    122. {
    123. php_varstream_data *data = stream->abstract;
    124. switch (whence) {
    125. case SEEK_SET:
    126. data->position = offset;
    127. break;
    128. case SEEK_CUR:
    129. data->position += offset;
    130. break;
    131. case SEEK_END:
    132. {
    133. zval **var;
    134. size_t curlen = 0;
    135. if (zend_hash_find(&EG(symbol_table),
    136. data->varname, data->varname_len + 1,
    137. (void**)&var) == SUCCESS) {
    138. if (Z_TYPE_PP(var) == IS_STRING) {
    139. curlen = Z_STRLEN_PP(var);
    140. } else {
    141. zval copyval = **var;
    142. zval_copy_ctor(&copyval);
    143. convert_to_string(&copyval);
    144. curlen = Z_STRLEN(copyval);
    145. zval_dtor(&copyval);
    146. }
    147. }
    148. data->position = curlen + offset;
    149. break;
    150. }
    151. }
    152. /* 防止随机访问指针移动到缓冲区开始位置之前 */
    153. if (data->position < 0) {
    154. data->position = 0;
    155. }
    156. if (newoffset) {
    157. *newoffset = data->position;
    158. }
    159. return 0;
    160. }
    161. static php_stream_ops php_varstream_ops = {
    162. php_varstream_write,
    163. php_varstream_read,
    164. php_varstream_closer,
    165. php_varstream_flush,
    166. PHP_VARSTREAM_STREAMTYPE,
    167. php_varstream_seek,
    168. NULL, /* cast */
    169. NULL, /* stat */
    170. NULL, /* set_option */
    171. };
    172. /* Define the wrapper operations */
    173. static php_stream *php_varstream_opener(
    174. php_stream_wrapper *wrapper,
    175. char *filename, char *mode, int options,
    176. char **opened_path, php_stream_context *context
    177. STREAMS_DC TSRMLS_DC)
    178. {
    179. php_varstream_data *data;
    180. php_url *url;
    181. if (options & STREAM_OPEN_PERSISTENT) {
    182. /* 按照变量流的定义, 是不能持久化的
    183. * 因为变量在请求结束后将被释放
    184. */
    185. php_stream_wrapper_log_error(wrapper, options
    186. TSRMLS_CC, "Unable to open %s persistently",
    187. filename);
    188. return NULL;
    189. }
    190. /* 标准URL解析: scheme://user:pass@host:port/path?query#fragment */
    191. url = php_url_parse(filename);
    192. if (!url) {
    193. php_stream_wrapper_log_error(wrapper, options
    194. TSRMLS_CC, "Unexpected error parsing URL");
    195. return NULL;
    196. }
    197. /* 检查是否有变量流URL必须的元素host, 以及scheme是否是var */
    198. if (!url->host || (url->host[0] == 0) ||
    199. strcasecmp("var", url->scheme) != 0) {
    200. /* Bad URL or wrong wrapper */
    201. php_stream_wrapper_log_error(wrapper, options
    202. TSRMLS_CC, "Invalid URL, must be in the form: "
    203. "var://variablename");
    204. php_url_free(url);
    205. return NULL;
    206. }
    207. /* 创建一个数据结构保存协议信息(变量流协议重要是变量名, 变量名长度, 当前偏移量) */
    208. data = emalloc(sizeof(php_varstream_data));
    209. data->position = 0;
    210. data->varname_len = strlen(url->host);
    211. data->varname = estrndup(url->host, data->varname_len + 1);
    212. /* 释放前面解析出来的url占用的内存 */
    213. php_url_free(url);
    214. /* 实例化一个流, 为其赋予恰当的流ops, 绑定抽象数据 */
    215. return php_stream_alloc(&php_varstream_ops, data, 0, mode);
    216. }
    217. static php_stream_wrapper_ops php_varstream_wrapper_ops = {
    218. php_varstream_opener, /* 调用php_stream_open_wrapper(sprintf("%s://xxx", PHP_VARSTREAM_WRAPPER))时执行 */
    219. NULL, /* stream_close */
    220. NULL, /* stream_stat */
    221. NULL, /* url_stat */
    222. NULL, /* dir_opener */
    223. PHP_VARSTREAM_WRAPPER,
    224. NULL, /* unlink */
    225. #if PHP_MAJOR_VERSION >= 5
    226. /* PHP >= 5.0 only */
    227. NULL, /* rename */
    228. NULL, /* mkdir */
    229. NULL, /* rmdir */
    230. #endif
    231. };
    232. static php_stream_wrapper php_varstream_wrapper = {
    233. &php_varstream_wrapper_ops,
    234. NULL, /* abstract */
    235. 0, /* is_url */
    236. };
    237. PHP_MINIT_FUNCTION(varstream)
    238. {
    239. /* 注册流包装器:
    240. * 1. 检查流包装器名字是否正确(符合这个正则: /^[a-zA-Z0-9+.-]+$/)
    241. * 2. 将传入的php_varstream_wrapper增加到url_stream_wrappers_hash这个HashTable中, key为PHP_VARSTREAM_WRAPPER
    242. */
    243. if (php_register_url_stream_wrapper(PHP_VARSTREAM_WRAPPER,
    244. &php_varstream_wrapper TSRMLS_CC)==FAILURE) {
    245. return FAILURE;
    246. }
    247. return SUCCESS;
    248. }
    249. PHP_MSHUTDOWN_FUNCTION(varstream)
    250. {
    251. /* 卸载流包装器: 从url_stream_wrappers_hash中删除 */
    252. if (php_unregister_url_stream_wrapper(PHP_VARSTREAM_WRAPPER
    253. TSRMLS_CC) == FAILURE) {
    254. return FAILURE;
    255. }
    256. return SUCCESS;
    257. }
    258. zend_module_entry varstream_module_entry = {
    259. #if ZEND_MODULE_API_NO >= 20010901
    260. STANDARD_MODULE_HEADER,
    261. #endif
    262. "varstream",
    263. NULL,
    264. PHP_MINIT(varstream),
    265. PHP_MSHUTDOWN(varstream),
    266. NULL,
    267. NULL,
    268. #if ZEND_MODULE_API_NO >= 20010901
    269. "0.1",
    270. #endif
    271. STANDARD_MODULE_PROPERTIES
    272. };
    273. #ifdef COMPILE_DL_VARSTREAM
    274. ZEND_GET_MODULE(varstream)
    275. #endif

    在构建加载扩展后, php就可以处理以var://开始的URL的请求, 它的行为和手册中用户空间实现的行为一致.

    内部实现

    首先你注意到的可能是这个扩展完全没有暴露用户空间函数. 它所做的只是在MINIT函数中调用了一个核心PHPAPI的钩子, 将var协议和我们定义的包装器关联起来:

    在本章前面你已经知道, 调用用户空间函数比如fopen将通过这个包装器的ops元素得到php_varstream_wrapper_ops, 这样去调用流的打开函数php_varstream_opener.

    这个函数的第一块代码检查是否请求持久化的流:

    1. if (options & STREAM_OPEN_PERSISTENT) {

    对于很多包装器这样的请求是合法的. 然而目前的情况这个行为没有意义. 一方面用户空间变量的定义就是临时的, 另一方面, varstream的实例化代价很低, 这就使得持久化的优势很小.

    像流包装层报告错误很简单, 只需要返回一个NULL值而不是流实例即可. 流包装层透出到用户空间的失败消息并不会说明具体的错误, 只是说明不能打开URL. 要想给开发者暴露更多的错误信息, 可以在返回之前使用php_stream_wrapper_log_error()函数.

    1. php_stream_wrapper_log_error(wrapper, options
    2. TSRMLS_CC, "Unable to open %s persistently",
    3. filename);
    4. return NULL;

    最后, varstream包装器创建了一个数据结构, 保存了流指向的变量名, 读取时的当前位置. 这个结构体将在流的读取和写入函数中用于获取变量, 并且将在流结束使用时由php_varstream_close函数释放.

    opendir()

    读写变量内容的实现可以再次进行扩展. 这里可以加入一个新的特性, 允许使用目录函数读取数组中的key. 在你的php_varstream_wrapper_ops结构体之前增加下面的代码:

    1. static size_t php_varstream_readdir(php_stream *stream,
    2. char *buf, size_t count TSRMLS_DC)
    3. {
    4. php_stream_dirent *ent = (php_stream_dirent*)buf;
    5. php_varstream_dirdata *data = stream->abstract;
    6. char *key;
    7. int type, key_len;
    8. long idx;
    9. /* 查找数组中的key */
    10. type = zend_hash_get_current_key_ex(Z_ARRVAL_P(data->arr),
    11. &key, &key_len, &idx, 0, &(data->pos));
    12. /* 字符串key */
    13. if (type == HASH_KEY_IS_STRING) {
    14. if (key_len >= sizeof(ent->d_name)) {
    15. /* truncate long keys to maximum length */
    16. key_len = sizeof(ent->d_name) - 1;
    17. }
    18. /* 设置到目录结构上 */
    19. memcpy(ent->d_name, key, key_len);
    20. ent->d_name[key_len] = 0;
    21. /* 数值key */
    22. } else if (type == HASH_KEY_IS_LONG) {
    23. /* 设置到目录结构上 */
    24. snprintf(ent->d_name, sizeof(ent->d_name), "%ld",idx);
    25. } else {
    26. /* 迭代结束 */
    27. return 0;
    28. }
    29. /* 移动数组指针(位置记录到流的抽象结构中) */
    30. zend_hash_move_forward_ex(Z_ARRVAL_P(data->arr),
    31. &data->pos);
    32. return sizeof(php_stream_dirent);
    33. }
    34. static int php_varstream_closedir(php_stream *stream,
    35. int close_handle TSRMLS_DC)
    36. {
    37. php_varstream_dirdata *data = stream->abstract;
    38. zval_ptr_dtor(&(data->arr));
    39. efree(data);
    40. return 0;
    41. }
    42. static int php_varstream_dirseek(php_stream *stream,
    43. off_t offset, int whence,
    44. off_t *newoffset TSRMLS_DC)
    45. {
    46. php_varstream_dirdata *data = stream->abstract;
    47. if (whence == SEEK_SET && offset == 0) {
    48. /* 重置数组指针 */
    49. zend_hash_internal_pointer_reset_ex(
    50. Z_ARRVAL_P(data->arr), &(data->pos));
    51. if (newoffset) {
    52. *newoffset = 0;
    53. }
    54. return 0;
    55. }
    56. /* 不支持其他类型的随机访问 */
    57. return -1;
    58. }
    59. static php_stream_ops php_varstream_dirops = {
    60. NULL, /* write */
    61. php_varstream_readdir,
    62. php_varstream_closedir,
    63. NULL, /* flush */
    64. PHP_VARSTREAM_DIRSTREAMTYPE,
    65. php_varstream_dirseek,
    66. NULL, /* cast */
    67. NULL, /* stat */
    68. NULL, /* set_option */
    69. };
    70. static php_stream *php_varstream_opendir(
    71. php_stream_wrapper *wrapper,
    72. char *filename, char *mode, int options,
    73. char **opened_path, php_stream_context *context
    74. STREAMS_DC TSRMLS_DC)
    75. {
    76. php_varstream_dirdata *data;
    77. php_url *url;
    78. zval **var;
    79. /* 不支持持久化流 */
    80. if (options & STREAM_OPEN_PERSISTENT) {
    81. php_stream_wrapper_log_error(wrapper, options
    82. TSRMLS_CC, "Unable to open %s persistently",
    83. filename);
    84. return NULL;
    85. }
    86. /* 解析URL */
    87. url = php_url_parse(filename);
    88. if (!url) {
    89. php_stream_wrapper_log_error(wrapper, options
    90. TSRMLS_CC, "Unexpected error parsing URL");
    91. return NULL;
    92. }
    93. /* 检查请求URL的正确性 */
    94. if (!url->host || (url->host[0] == 0) ||
    95. strcasecmp("var", url->scheme) != 0) {
    96. /* Bad URL or wrong wrapper */
    97. php_stream_wrapper_log_error(wrapper, options
    98. TSRMLS_CC, "Invalid URL, must be in the form: "
    99. "var://variablename");
    100. php_url_free(url);
    101. return NULL;
    102. }
    103. /* 查找变量 */
    104. if (zend_hash_find(&EG(symbol_table), url->host,
    105. strlen(url->host) + 1, (void**)&var) == FAILURE) {
    106. php_stream_wrapper_log_error(wrapper, options
    107. TSRMLS_CC, "Variable $%s not found", url->host);
    108. php_url_free(url);
    109. return NULL;
    110. }
    111. /* 检查变量类型 */
    112. if (Z_TYPE_PP(var) != IS_ARRAY) {
    113. php_stream_wrapper_log_error(wrapper, options
    114. TSRMLS_CC, "$%s is not an array", url->host);
    115. php_url_free(url);
    116. return NULL;
    117. }
    118. /* 释放前面分配的URL结构 */
    119. php_url_free(url);
    120. /* 分配抽象数据结构 */
    121. data = emalloc(sizeof(php_varstream_dirdata));
    122. if ( Z_ISREF_PP(var) && Z_REFCOUNT_PP(var) > 1) {
    123. /* 全拷贝 */
    124. MAKE_STD_ZVAL(data->arr);
    125. *(data->arr) = **var;
    126. zval_copy_ctor(data->arr);
    127. INIT_PZVAL(data->arr);
    128. } else {
    129. /* 写时拷贝 */
    130. data->arr = *var;
    131. Z_SET_REFCOUNT_P(data->arr, Z_REFCOUNT_P(data->arr) + 1);
    132. }
    133. /* 重置数组指针 */
    134. zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(data->arr),
    135. &data->pos);
    136. return php_stream_alloc(&php_varstream_dirops,data,0,mode);
    137. }

    现在, 将你的php_varstream_wrapper_ops结构体中的dir_opener的NULL替换成你的php_varstream_opendir函数. 最后, 将下面新定义的类型放入到你的php_varstream.h文件的php_varstream_data定义下面:

    1. #define PHP_VARSTREAM_DIRSTREAMTYPE "varstream directory"
    2. typedef struct _php_varstream_dirdata {
    3. zval *arr;
    4. HashPosition pos;

    在你基于fopen()实现的varstream包装器中, 你直接使用持久变量名, 每次执行读写操作时从符号表中获取变量. 而这里, opendir()的实现中获取变量时处理了变量不存在或者类型错误的异常. 你还有一个数组变量的拷贝, 这就说明原数组的改变并不会影响后续的readdir()调用的结果. 原来存储变量名的方式也可以正常工作, 这里只是给出另外一种选择作为演示示例.

    实际上, 目录流并没有使用SEEK_CUR, SEEK_END, 或者除了0之外的偏移量. 在实现目录流操作时, 最好还是涉及你的函数能以某种方式处理这些情况, 以使得在流包装层变化时能够适应其目录随机访问.