preface

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

get_iterator handler

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

struct _zend_object_iterator {
    void *data;
    zend_object_iterator_funcs *funcs;
    ulong index; /* private to fe_reset/fe_fetch opcodes */
};

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

typedef struct _zend_object_iterator_funcs {
    /* release all resources associated with this iterator instance */
    void (*dtor)(zend_object_iterator *iter TSRMLS_DC);

    /* check for end of iteration (FAILURE or SUCCESS if data is valid) */
    int (*valid)(zend_object_iterator *iter TSRMLS_DC);

    /* fetch the item data for the current element */
    void (*get_current_data)(zend_object_iterator *iter, zval ***data TSRMLS_DC);

    /* fetch the key for the current element (optional, may be NULL). The key
     * should be written into the provided zval* using the ZVAL_* macros. If
     * this handler is not provided auto-incrementing integer keys will be
     * used. */
    void (*get_current_key)(zend_object_iterator *iter, zval *key TSRMLS_DC);

    /* step forwards to next element */
    void (*move_forward)(zend_object_iterator *iter TSRMLS_DC);

    /* rewind to start of data (optional, may be NULL) */
    void (*rewind)(zend_object_iterator *iter TSRMLS_DC);

    /* invalidate current value/key (optional, may be NULL) */
    void (*invalidate_current)(zend_object_iterator *iter TSRMLS_DC);
} zend_object_iterator_funcs;

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

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

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

typedef struct _buffer_view_iterator {
	zend_object_iterator intern;
	buffer_view_object *view;
	size_t offset;
	zval *current;
} buffer_view_iterator;

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

static zend_object_iterator_funcs linger_buffer_view_iterator_funcs = {
	linger_buffer_view_iterator_dtor,
	linger_buffer_view_iterator_valid,
	linger_buffer_view_iterator_get_current_data,
	linger_buffer_view_iterator_get_current_key,
	linger_buffer_view_iterator_move_forward,
	linger_buffer_view_iterator_rewind
};

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

zend_object_iterator *linger_buffer_view_get_iterator(zend_class_entry *ce, zval *object, int by_ref TSRMLS_DC)
{
	buffer_view_iterator *iterator;
	if (by_ref) {
		zend_throw_exception(NULL, "Cannot interate buffer view by refererce", 0 TSRMLS_CC);
		return NULL;
	}
	iterator = emalloc(sizeof(buffer_view_iterator));
	iterator->intern.funcs = &linger_buffer_view_iterator_funcs;
	iterator->intern.data = object;
	Z_ADDREF_P(object);

	iterator->view = zend_object_store_get_object(object TSRMLS_CC);
	iterator->offset = 0;
	iterator->current = NULL;
	return (zend_object_iterator *) iterator;
}

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

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

Iterator functions

static void linger_buffer_view_iterator_dtor(zend_object_iterator *intern TSRMLS_DC)
{
	buffer_view_iterator *iterator = (buffer_view_iterator *) intern;
	if (iterator->current) {
		zval_ptr_dtor(&iterator->current);
	}
	zval_ptr_dtor((zval **) &intern->data);
	efree(iterator);
}

static int linger_buffer_view_iterator_valid(zend_object_iterator *intern TSRMLS_DC)
{
	buffer_view_iterator *iterator = (buffer_view_iterator *)intern;
	return iterator->offset < iterator->view->length ? SUCCESS : FAILURE;
}

static void linger_buffer_view_iterator_get_current_data(zend_object_iterator *intern, zval ***data TSRMLS_DC)
{
	buffer_view_iterator *iterator = (buffer_view_iterator *)intern;
	if (iterator->current) {
		zval_ptr_dtor(&iterator->current);
	}

	if (iterator->offset < iterator->view->length) {
		iterator->current = linger_buffer_view_offset_get(iterator->view, iterator->offset);
		*data = &iterator->current;
	} else {
		*data = NULL;
	}
}

static void linger_buffer_view_iterator_get_current_key(zend_object_iterator *intern, zval *key TSRMLS_DC)
{
	buffer_view_iterator *iterator = (buffer_view_iterator *)intern;
	ZVAL_LONG(key, iterator->offset);
}

static void linger_buffer_view_iterator_move_forward(zend_object_iterator *intern TSRMLS_DC)
{
	buffer_view_iterator *iterator = (buffer_view_iterator *)intern;
	iterator->offset++;
}

static void linger_buffer_view_iterator_rewind(zend_object_iterator *intern TSRMLS_DC)
{
	buffer_view_iterator *iterator = (buffer_view_iterator *) iterator;
	iterator->offset = 0;
	iterator->current = NULL;
}

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

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