preface

在之前的文章中,我们已经实现了一些 object handlers 来将我们的 ArrayBuffer 整合到 php 中。但是美中不足的是,我们的 ArrayBufferView 并不支持迭代器操作。也就是它不能像 php 中的数组那样使用foreach来遍历。 那么,我们接下来就来看看迭代器在内核中是如何实现的,并且给我们的 ArrayBufferView 也增加一个迭代器。

get_iterator handler

内核中的迭代器跟用户端的IteratorAggregate接口功能是一样的。一个具有迭代功能的类都有一个get_iterator处理器,它会返回一个zend_object_iterator *类型的结构,该结构定义如下(位于 phpsrc/Zend/zend_iterators.h 中):

1struct _zend_object_iterator {
2    void *data;
3    zend_object_iterator_funcs *funcs;
4    ulong index; /* private to fe_reset/fe_fetch opcodes */
5};

其中的index成员就是内核中用以实现foreach的,它的值会在每次迭代后增加。funcs成员包含了不同的迭代操作:

 1typedef struct _zend_object_iterator_funcs {
 2    /* release all resources associated with this iterator instance */
 3    void (*dtor)(zend_object_iterator *iter TSRMLS_DC);
 4
 5    /* check for end of iteration (FAILURE or SUCCESS if data is valid) */
 6    int (*valid)(zend_object_iterator *iter TSRMLS_DC);
 7
 8    /* fetch the item data for the current element */
 9    void (*get_current_data)(zend_object_iterator *iter, zval ***data TSRMLS_DC);
10
11    /* fetch the key for the current element (optional, may be NULL). The key
12     * should be written into the provided zval* using the ZVAL_* macros. If
13     * this handler is not provided auto-incrementing integer keys will be
14     * used. */
15    void (*get_current_key)(zend_object_iterator *iter, zval *key TSRMLS_DC);
16
17    /* step forwards to next element */
18    void (*move_forward)(zend_object_iterator *iter TSRMLS_DC);
19
20    /* rewind to start of data (optional, may be NULL) */
21    void (*rewind)(zend_object_iterator *iter TSRMLS_DC);
22
23    /* invalidate current value/key (optional, may be NULL) */
24    void (*invalidate_current)(zend_object_iterator *iter TSRMLS_DC);
25} zend_object_iterator_funcs;

这些处理器跟Iterator接口中的抽象方法类似,只不过名字不同罢了。唯一没有对应用户端接口的是invalidate_current,它可以被用来销毁当前的key/value。 然而这个操作几乎不会被用到,通常foreach也不会取调用它。

最后一个成员data可以用来存放一些自定义的数据。通常类似于zend_object的操作,我们需要对它的结构进行扩展。

为了给 ArrayBufferView 添加迭代器,我们需要保存一些信息:首先我们需要有一个 buffer view 对象的引用,我们可以用data来存放该引用。其次我们还需要存储buffer_view_object, 这样可以避免我们在每种迭代操作中都要获取它。最后我们还需要存放当前元素的offset和当前元素的zval *

1typedef struct _buffer_view_iterator {
2	zend_object_iterator intern;
3	buffer_view_object *view;
4	size_t offset;
5	zval *current;
6} buffer_view_iterator;

下面我们来声明一个zend_object_iterator_funcs结构体:

1static zend_object_iterator_funcs linger_buffer_view_iterator_funcs = {
2	linger_buffer_view_iterator_dtor,
3	linger_buffer_view_iterator_valid,
4	linger_buffer_view_iterator_get_current_data,
5	linger_buffer_view_iterator_get_current_key,
6	linger_buffer_view_iterator_move_forward,
7	linger_buffer_view_iterator_rewind
8};

接下来我们来实现get_iterator handler。这个处理器接受一个class entry,一个object,还有一个标记迭代是否为引用,然后返回 zend_object_iterator *。我们要做的就是创建一个 iterator,并对其中的元素做初始化:

 1zend_object_iterator *linger_buffer_view_get_iterator(zend_class_entry *ce, zval *object, int by_ref TSRMLS_DC)
 2{
 3	buffer_view_iterator *iterator;
 4	if (by_ref) {
 5		zend_throw_exception(NULL, "Cannot interate buffer view by refererce", 0 TSRMLS_CC);
 6		return NULL;
 7	}
 8	iterator = emalloc(sizeof(buffer_view_iterator));
 9	iterator->intern.funcs = &linger_buffer_view_iterator_funcs;
10	iterator->intern.data = object;
11	Z_ADDREF_P(object);
12
13	iterator->view = zend_object_store_get_object(object TSRMLS_CC);
14	iterator->offset = 0;
15	iterator->current = NULL;
16	return (zend_object_iterator *) iterator;
17}

然后我们需要调整注册 buffer view class 的宏函数

1#define REGISTER_ARRAY_BUFFER_VIEW_CLASS(class_name, type)		                 \
2	do {														                 \
3		INIT_CLASS_ENTRY(ce, #class_name, linger_array_buffer_view_methods);	 \
4		type##_array_ce = zend_register_internal_class(&ce TSRMLS_CC);           \
5		type##_array_ce->create_object = linger_array_buffer_view_create_object; \
6		type##_array_ce->get_iterator = linger_buffer_view_get_iterator;		 \
7		type##_array_ce->iterator_funcs.funcs = &linger_buffer_view_iterator_funcs; \
8		zend_class_implements(type##_array_ce TSRMLS_CC, 1, zend_ce_traversable); \
9	} while (0)

Iterator functions

 1static void linger_buffer_view_iterator_dtor(zend_object_iterator *intern TSRMLS_DC)
 2{
 3	buffer_view_iterator *iterator = (buffer_view_iterator *) intern;
 4	if (iterator->current) {
 5		zval_ptr_dtor(&iterator->current);
 6	}
 7	zval_ptr_dtor((zval **) &intern->data);
 8	efree(iterator);
 9}
10
11static int linger_buffer_view_iterator_valid(zend_object_iterator *intern TSRMLS_DC)
12{
13	buffer_view_iterator *iterator = (buffer_view_iterator *)intern;
14	return iterator->offset < iterator->view->length ? SUCCESS : FAILURE;
15}
16
17static void linger_buffer_view_iterator_get_current_data(zend_object_iterator *intern, zval ***data TSRMLS_DC)
18{
19	buffer_view_iterator *iterator = (buffer_view_iterator *)intern;
20	if (iterator->current) {
21		zval_ptr_dtor(&iterator->current);
22	}
23
24	if (iterator->offset < iterator->view->length) {
25		iterator->current = linger_buffer_view_offset_get(iterator->view, iterator->offset);
26		*data = &iterator->current;
27	} else {
28		*data = NULL;
29	}
30}
31
32static void linger_buffer_view_iterator_get_current_key(zend_object_iterator *intern, zval *key TSRMLS_DC)
33{
34	buffer_view_iterator *iterator = (buffer_view_iterator *)intern;
35	ZVAL_LONG(key, iterator->offset);
36}
37
38static void linger_buffer_view_iterator_move_forward(zend_object_iterator *intern TSRMLS_DC)
39{
40	buffer_view_iterator *iterator = (buffer_view_iterator *)intern;
41	iterator->offset++;
42}
43
44static void linger_buffer_view_iterator_rewind(zend_object_iterator *intern TSRMLS_DC)
45{
46	buffer_view_iterator *iterator = (buffer_view_iterator *) iterator;
47	iterator->offset = 0;
48	iterator->current = NULL;
49}

代码依然平淡无奇,所以没什么好解释的。

至此我们就完成了 ArrayBufferView 类的迭代器操作。完整代码可以访问:https://github.com/iliubang/php-ArrayBuffer.git