实现一个包装器

为了演示包装器和流操作的内部工作原理, 我们需要重新实现php手册的stream_wrapper_register()一页示例中的var://包装器.

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

译注: 为了方便大家阅读, 对代码的注释进行了适量补充调整, 此外, 由于phpapi的调整, 原著中的代码不能直接在译者使用的php-5.4.10中运行, 进行了适当的修改. 因此下面代码结构可能和原著略有不同, 请参考阅读.(下面opendir的例子也进行了相应的修改)

config.m4

  1. PHP_ARG_ENABLE(varstream,whether to enable varstream support,
  2. [ enable-varstream Enable varstream support])
  3. if test "$PHP_VARSTREAM" = "yes"; then
  4. AC_DEFINE(HAVE_VARSTREAM,1,[Whether you want varstream])
  5. PHP_NEW_EXTENSION(varstream, varstream.c, $ext_shared)
  6. fi

php_varstream.h

  1. #ifndef PHP_VARSTREAM_H
  2. #define PHP_VARSTREAM_H
  3. extern zend_module_entry varstream_module_entry;
  4. #define phpext_varstream_ptr &varstream_module_entry
  5. #ifdef PHP_WIN32
  6. # define PHP_VARSTREAM_API __declspec(dllexport)
  7. #elif defined(__GNUC__) && __GNUC__ >= 4
  8. # define PHP_VARSTREAM_API __attribute__ ((visibility("default")))
  9. #else
  10. # define PHP_VARSTREAM_API
  11. #endif
  12. #ifdef ZTS
  13. #include "TSRM.h"
  14. #endif
  15. PHP_MINIT_FUNCTION(varstream);
  16. PHP_MSHUTDOWN_FUNCTION(varstream);
  17. #define PHP_VARSTREAM_WRAPPER "var"
  18. #define PHP_VARSTREAM_STREAMTYPE "varstream"
  19. /* 变量流的抽象数据结构 */
  20. typedef struct _php_varstream_data {
  21. off_t position;
  22. char *varname;
  23. int varname_len;
  24. } php_varstream_data;
  25. #ifdef ZTS
  26. #define VARSTREAM_G(v) TSRMG(varstream_globals_id, zend_varstream_globals *, v)
  27. #else
  28. #define VARSTREAM_G(v) (varstream_globals.v)
  29. #endif
  30. #endif

varstream.c

  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. }
  116. } else {
  117. data->position = 0;
  118. }
  119. return 0;
  120. }
  121. static int php_varstream_seek(php_stream *stream, off_t offset,
  122. int whence, off_t *newoffset TSRMLS_DC)
  123. {
  124. php_varstream_data *data = stream->abstract;
  125. switch (whence) {
  126. case SEEK_SET:
  127. data->position = offset;
  128. break;
  129. case SEEK_CUR:
  130. data->position += offset;
  131. break;
  132. case SEEK_END:
  133. {
  134. zval **var;
  135. size_t curlen = 0;
  136. if (zend_hash_find(&EG(symbol_table),
  137. data->varname, data->varname_len + 1,
  138. (void**)&var) == SUCCESS) {
  139. if (Z_TYPE_PP(var) == IS_STRING) {
  140. curlen = Z_STRLEN_PP(var);
  141. } else {
  142. zval copyval = **var;
  143. zval_copy_ctor(&copyval);
  144. convert_to_string(&copyval);
  145. curlen = Z_STRLEN(copyval);
  146. zval_dtor(&copyval);
  147. }
  148. }
  149. data->position = curlen + offset;
  150. break;
  151. }
  152. }
  153. /* 防止随机访问指针移动到缓冲区开始位置之前 */
  154. if (data->position < 0) {
  155. data->position = 0;
  156. }
  157. if (newoffset) {
  158. *newoffset = data->position;
  159. }
  160. return 0;
  161. }
  162. static php_stream_ops php_varstream_ops = {
  163. php_varstream_write,
  164. php_varstream_read,
  165. php_varstream_closer,
  166. php_varstream_flush,
  167. PHP_VARSTREAM_STREAMTYPE,
  168. php_varstream_seek,
  169. NULL, /* cast */
  170. NULL, /* stat */
  171. NULL, /* set_option */
  172. };
  173. /* Define the wrapper operations */
  174. static php_stream *php_varstream_opener(
  175. php_stream_wrapper *wrapper,
  176. char *filename, char *mode, int options,
  177. char **opened_path, php_stream_context *context
  178. STREAMS_DC TSRMLS_DC)
  179. {
  180. php_varstream_data *data;
  181. php_url *url;
  182. if (options & STREAM_OPEN_PERSISTENT) {
  183. /* 按照变量流的定义, 是不能持久化的
  184. * 因为变量在请求结束后将被释放
  185. */
  186. php_stream_wrapper_log_error(wrapper, options
  187. TSRMLS_CC, "Unable to open %s persistently",
  188. filename);
  189. return NULL;
  190. }
  191. /* 标准URL解析: scheme://user:pass@host:port/path?query#fragment */
  192. url = php_url_parse(filename);
  193. if (!url) {
  194. php_stream_wrapper_log_error(wrapper, options
  195. TSRMLS_CC, "Unexpected error parsing URL");
  196. return NULL;
  197. }
  198. /* 检查是否有变量流URL必须的元素host, 以及scheme是否是var */
  199. if (!url->host || (url->host[0] == 0) ||
  200. strcasecmp("var", url->scheme) != 0) {
  201. /* Bad URL or wrong wrapper */
  202. php_stream_wrapper_log_error(wrapper, options
  203. TSRMLS_CC, "Invalid URL, must be in the form: "
  204. "var://variablename");
  205. php_url_free(url);
  206. return NULL;
  207. }
  208. /* 创建一个数据结构保存协议信息(变量流协议重要是变量名, 变量名长度, 当前偏移量) */
  209. data = emalloc(sizeof(php_varstream_data));
  210. data->position = 0;
  211. data->varname_len = strlen(url->host);
  212. data->varname = estrndup(url->host, data->varname_len + 1);
  213. /* 释放前面解析出来的url占用的内存 */
  214. php_url_free(url);
  215. /* 实例化一个流, 为其赋予恰当的流ops, 绑定抽象数据 */
  216. return php_stream_alloc(&php_varstream_ops, data, 0, mode);
  217. }
  218. static php_stream_wrapper_ops php_varstream_wrapper_ops = {
  219. php_varstream_opener, /* 调用php_stream_open_wrapper(sprintf("%s://xxx", PHP_VARSTREAM_WRAPPER))时执行 */
  220. NULL, /* stream_close */
  221. NULL, /* stream_stat */
  222. NULL, /* url_stat */
  223. NULL, /* dir_opener */
  224. PHP_VARSTREAM_WRAPPER,
  225. NULL, /* unlink */
  226. #if PHP_MAJOR_VERSION >= 5
  227. /* PHP >= 5.0 only */
  228. NULL, /* rename */
  229. NULL, /* mkdir */
  230. NULL, /* rmdir */
  231. #endif
  232. };
  233. static php_stream_wrapper php_varstream_wrapper = {
  234. &php_varstream_wrapper_ops,
  235. NULL, /* abstract */
  236. 0, /* is_url */
  237. };
  238. PHP_MINIT_FUNCTION(varstream)
  239. {
  240. /* 注册流包装器:
  241. * 1. 检查流包装器名字是否正确(符合这个正则: /^[a-zA-Z0-9+.-]+$/)
  242. * 2. 将传入的php_varstream_wrapper增加到url_stream_wrappers_hash这个HashTable中, key为PHP_VARSTREAM_WRAPPER
  243. */
  244. if (php_register_url_stream_wrapper(PHP_VARSTREAM_WRAPPER,
  245. &php_varstream_wrapper TSRMLS_CC)==FAILURE) {
  246. return FAILURE;
  247. }
  248. return SUCCESS;
  249. }
  250. PHP_MSHUTDOWN_FUNCTION(varstream)
  251. {
  252. /* 卸载流包装器: 从url_stream_wrappers_hash中删除 */
  253. if (php_unregister_url_stream_wrapper(PHP_VARSTREAM_WRAPPER
  254. TSRMLS_CC) == FAILURE) {
  255. return FAILURE;
  256. }
  257. return SUCCESS;
  258. }
  259. zend_module_entry varstream_module_entry = {
  260. #if ZEND_MODULE_API_NO >= 20010901
  261. STANDARD_MODULE_HEADER,
  262. #endif
  263. "varstream",
  264. NULL,
  265. PHP_MINIT(varstream),
  266. PHP_MSHUTDOWN(varstream),
  267. NULL,
  268. NULL,
  269. NULL,
  270. #if ZEND_MODULE_API_NO >= 20010901
  271. "0.1",
  272. #endif
  273. STANDARD_MODULE_PROPERTIES
  274. };
  275. #ifdef COMPILE_DL_VARSTREAM
  276. ZEND_GET_MODULE(varstream)
  277. #endif

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

内部实现

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

  1. static php_stream_wrapper php_varstream_wrapper = {
  2. &php_varstream_wrapper_ops,
  3. NULL, /* abstract */
  4. 0, /* is_url */
  5. }

很明显, 最重要的元素就是ops, 它提供了访问特定流包装器的创建以及检查函数. 你可以安全的忽略abstract属性, 它仅在运行时使用, 在初始化定义时, 它只是作为一个占位符. 第三个元素is_url, 它告诉php在使用这个包装器时是否考虑php.ini中的allow_url_fopen选项. 如果这个值非0, 并且将allow_url_fopen设置为false, 则这个包装器不能被脚本使用.

在本章前面你已经知道, 调用用户空间函数比如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;

URL解析

实例化varstream的下一步需要一个人类可读的URL, 将它分块放入到一个易管理的结构体中. 幸运的是它使用了和用户空间url_parse()函数相同的机制. 如果URL成功解析, 将会分配一个php_url结构体并设置合适的值. 如果在URL中没有某些值, 在返回的php_url中对应的将被设置为NULL. 这个结构体必须在离开php_varstream_opener函数之前被显式释放, 否则它的内存将会泄露:

  1. typedef struct php_url {
  2. /* scheme://user:pass@host:port/path?query#fragment */
  3. char *scheme;
  4. char *user;
  5. char *pass;
  6. char *host;
  7. unsigned short port;
  8. char *path;
  9. char *query;
  10. char *fragment;
  11. } php_url;

最后, 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;
  5. } php_varstream_dirdata;

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

由于目录访问是基于成块的目录条目, 而不是字符, 因此这里需要一套独立的流操作. 这个版本中, write没有意义, 因此保持它为NULL. read的实现使用zend_hash_get_current_key_ex()函数将数组映射到目录名. 而随机访问也只是对SEEK_SET有效, 用来响应rewinddir()跳转到数组开始位置.

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