实现一个包装器
为了演示包装器和流操作的内部工作原理, 我们需要重新实现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
2
3
4
5
6
7
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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(©val);
INIT_PZVAL(©val);
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(©val);
}
/* 返回设置到缓冲区的字节数 */
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(©val);
convert_to_string(©val);
data->position = Z_STRLEN(copyval);
zval_dtor(©val);
}
} 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(©val);
convert_to_string(©val);
curlen = Z_STRLEN(copyval);
zval_dtor(©val);
}
}
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
在构建加载扩展后, php就可以处理以var://开始的URL的请求, 它的行为和手册中用户空间实现的行为一致.
内部实现
首先你注意到的可能是这个扩展完全没有暴露用户空间函数. 它所做的只是在MINIT函数中调用了一个核心PHPAPI的钩子, 将var协议和我们定义的包装器关联起来:
static php_stream_wrapper php_varstream_wrapper = {
&php_varstream_wrapper_ops,
NULL, /* abstract */
0, /* is_url */
}
2
3
4
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.
这个函数的第一块代码检查是否请求持久化的流:
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;
2
3
4
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;
2
3
4
5
6
7
8
9
10
11
最后, 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);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
现在, 将你的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;
2
3
4
5
在你基于fopen()实现的varstream包装器中, 你直接使用持久变量名, 每次执行读写操作时从符号表中获取变量. 而这里, opendir()的实现中获取变量时处理了变量不存在或者类型错误的异常. 你还有一个数组变量的拷贝, 这就说明原数组的改变并不会影响后续的readdir()调用的结果. 原来存储变量名的方式也可以正常工作, 这里只是给出另外一种选择作为演示示例.
由于目录访问是基于成块的目录条目, 而不是字符, 因此这里需要一套独立的流操作. 这个版本中, write没有意义, 因此保持它为NULL. read的实现使用zend_hash_get_current_key_ex()函数将数组映射到目录名. 而随机访问也只是对SEEK_SET有效, 用来响应rewinddir()跳转到数组开始位置.
实际上, 目录流并没有使用SEEK_CUR, SEEK_END, 或者除了0之外的偏移量. 在实现目录流操作时, 最好还是涉及你的函数能以某种方式处理这些情况, 以使得在流包装层变化时能够适应其目录随机访问.