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
评论