From ae8ef6c13abfe97acb7cbcbaa9608fe2cb8fdf23 Mon Sep 17 00:00:00 2001 From: Marcus Boerger Date: Tue, 22 Jul 2008 22:54:35 +0000 Subject: [PATCH] - MFH Add MultipleIterator (Arnaud, Marcus) --- NEWS | 1 + .../multipleiterator.inc | 4 +- ext/spl/internal/recursivetreeiterator.inc | 2 +- ext/spl/php_spl.c | 18 +- ext/spl/spl_iterators.h | 1 + ext/spl/spl_observer.c | 302 +++++++++++++++ ext/spl/spl_observer.h | 1 + ext/spl/tests/multiple_iterator_001.phpt | 345 ++++++++++++++++++ 8 files changed, 663 insertions(+), 11 deletions(-) rename ext/spl/{examples => internal}/multipleiterator.inc (99%) create mode 100755 ext/spl/tests/multiple_iterator_001.phpt diff --git a/NEWS b/NEWS index fb3c53abeb..d064998452 100644 --- a/NEWS +++ b/NEWS @@ -118,6 +118,7 @@ PHP NEWS . Added FixedArray. (Etienne, Tony) . Added delaying exceptions in SPL's autoload mechanism. (Marcus) . Added RecursiveTreeIterator. (Arnaud, Marcus) + . Added MultipleIterator. (Arnaud, Marcus, Johannes) - Improved Zend Engine: . Added "compact" handler for Zend MM storage. (Dmitry) diff --git a/ext/spl/examples/multipleiterator.inc b/ext/spl/internal/multipleiterator.inc similarity index 99% rename from ext/spl/examples/multipleiterator.inc rename to ext/spl/internal/multipleiterator.inc index cb0c1553d9..e977ca369c 100755 --- a/ext/spl/examples/multipleiterator.inc +++ b/ext/spl/internal/multipleiterator.inc @@ -1,6 +1,6 @@ [zval *obj, zval *inf] */ @@ -620,6 +623,297 @@ static const zend_function_entry spl_funcs_SplObjectStorage[] = { {NULL, NULL, NULL} }; +typedef enum { + MIT_NEED_ANY = 0, + MIT_NEED_ALL = 1, + MIT_KEYS_NUMERIC = 0, + MIT_KEYS_ASSOC = 2 +} MultipleIteratorFlags; + +#define SPL_MULTIPLE_ITERATOR_GET_ALL_CURRENT 1 +#define SPL_MULTIPLE_ITERATOR_GET_ALL_KEY 2 + +/* {{{ proto void MultipleIterator::__construct([int flags = MIT_NEED_ALL|MIT_KEYS_NUMERIC]) + Iterator that iterates over several iterators one after the other */ +SPL_METHOD(MultipleIterator, __construct) +{ + spl_SplObjectStorage *intern; + long flags = MIT_NEED_ALL|MIT_KEYS_NUMERIC; + + php_set_error_handling(EH_THROW, spl_ce_InvalidArgumentException TSRMLS_CC); + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &flags) == FAILURE) { + php_set_error_handling(EH_NORMAL, NULL TSRMLS_CC); + return; + } + + php_set_error_handling(EH_NORMAL, NULL TSRMLS_CC); + + intern = (spl_SplObjectStorage*)zend_object_store_get_object(getThis() TSRMLS_CC); + intern->flags = flags; +} +/* }}} */ + +/* {{{ proto int MultipleIterator::getFlags() + Return current flags */ +SPL_METHOD(MultipleIterator, getFlags) +{ + spl_SplObjectStorage *intern = (spl_SplObjectStorage*)zend_object_store_get_object(getThis() TSRMLS_CC); + RETURN_LONG(intern->flags); +} +/* }}} */ + +/* {{{ proto int MultipleIterator::setFlags(int flags) + Set flags */ +SPL_METHOD(MultipleIterator, setFlags) +{ + spl_SplObjectStorage *intern; + intern = (spl_SplObjectStorage*)zend_object_store_get_object(getThis() TSRMLS_CC); + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &intern->flags) == FAILURE) { + return; + } +} +/* }}} */ + +/* {{{ proto void attachIterator(Iterator iterator[, mixed info]) throws InvalidArgumentException + Attach a new iterator */ +SPL_METHOD(MultipleIterator, attachIterator) +{ + spl_SplObjectStorage *intern; + zval *iterator = NULL, *info = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O|z!", &iterator, zend_ce_iterator, &info) == FAILURE) { + return; + } + + intern = (spl_SplObjectStorage*)zend_object_store_get_object(getThis() TSRMLS_CC); + + if (info != NULL) { + spl_SplObjectStorageElement *element; + zval compare_result; + + if (Z_TYPE_P(info) != IS_LONG && Z_TYPE_P(info) != IS_STRING) { + zend_throw_exception(spl_ce_InvalidArgumentException, "Info must be NULL, integer or string", 0 TSRMLS_CC); + return; + } + + zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos); + while (zend_hash_get_current_data_ex(&intern->storage, (void**)&element, &intern->pos) == SUCCESS) { + is_identical_function(&compare_result, info, element->inf TSRMLS_CC); + if (Z_LVAL(compare_result)) { + zend_throw_exception(spl_ce_InvalidArgumentException, "Key duplication error", 0 TSRMLS_CC); + return; + } + zend_hash_move_forward_ex(&intern->storage, &intern->pos); + } + } + + spl_object_storage_attach(intern, iterator, info TSRMLS_CC); +} +/* }}} */ + +/* {{{ proto void MultipleIterator::rewind() + Rewind all attached iterator instances */ +SPL_METHOD(MultipleIterator, rewind) +{ + spl_SplObjectStorage *intern; + spl_SplObjectStorageElement *element; + zval *it; + + intern = (spl_SplObjectStorage*)zend_object_store_get_object(getThis() TSRMLS_CC); + + zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos); + while (zend_hash_get_current_data_ex(&intern->storage, (void**)&element, &intern->pos) == SUCCESS && !EG(exception)) { + it = element->obj; + zend_call_method_with_0_params(&it, Z_OBJCE_P(it), &Z_OBJCE_P(it)->iterator_funcs.zf_rewind, "rewind", NULL); + zend_hash_move_forward_ex(&intern->storage, &intern->pos); + } +} +/* }}} */ + +/* {{{ proto void MultipleIterator::next() + Move all attached iterator instances forward */ +SPL_METHOD(MultipleIterator, next) +{ + spl_SplObjectStorage *intern; + spl_SplObjectStorageElement *element; + zval *it; + + intern = (spl_SplObjectStorage*)zend_object_store_get_object(getThis() TSRMLS_CC); + + zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos); + while (zend_hash_get_current_data_ex(&intern->storage, (void**)&element, &intern->pos) == SUCCESS && !EG(exception)) { + it = element->obj; + zend_call_method_with_0_params(&it, Z_OBJCE_P(it), &Z_OBJCE_P(it)->iterator_funcs.zf_next, "next", NULL); + zend_hash_move_forward_ex(&intern->storage, &intern->pos); + } +} +/* }}} */ + +/* {{{ proto bool MultipleIterator::valid() + Return whether all or one sub iterator is valid depending on flags */ +SPL_METHOD(MultipleIterator, valid) +{ + spl_SplObjectStorage *intern; + spl_SplObjectStorageElement *element; + zval *it, *retval = NULL; + long expect, valid; + + intern = (spl_SplObjectStorage*)zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!zend_hash_num_elements(&intern->storage)) { + RETURN_FALSE; + } + + expect = (intern->flags & MIT_NEED_ALL) ? 1 : 0; + + zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos); + while (zend_hash_get_current_data_ex(&intern->storage, (void**)&element, &intern->pos) == SUCCESS && !EG(exception)) { + it = element->obj; + zend_call_method_with_0_params(&it, Z_OBJCE_P(it), &Z_OBJCE_P(it)->iterator_funcs.zf_valid, "valid", &retval); + + if (retval) { + valid = Z_LVAL_P(retval); + zval_ptr_dtor(&retval); + } else { + valid = 0; + } + + if (expect != valid) { + RETURN_BOOL(!expect); + } + + zend_hash_move_forward_ex(&intern->storage, &intern->pos); + } + + RETURN_BOOL(expect); +} +/* }}} */ + +static void spl_multiple_iterator_get_all(spl_SplObjectStorage *intern, int get_type, zval *return_value TSRMLS_DC) /* {{{ */ +{ + spl_SplObjectStorageElement *element; + zval *it, *retval = NULL; + int valid = 1, num_elements; + + num_elements = zend_hash_num_elements(&intern->storage); + if (num_elements < 1) { + RETURN_FALSE; + } + + array_init_size(return_value, num_elements); + + zend_hash_internal_pointer_reset_ex(&intern->storage, &intern->pos); + while (zend_hash_get_current_data_ex(&intern->storage, (void**)&element, &intern->pos) == SUCCESS && !EG(exception)) { + it = element->obj; + zend_call_method_with_0_params(&it, Z_OBJCE_P(it), &Z_OBJCE_P(it)->iterator_funcs.zf_valid, "valid", &retval); + + if (retval) { + valid = Z_LVAL_P(retval); + zval_ptr_dtor(&retval); + } else { + valid = 0; + } + + if (valid) { + if (SPL_MULTIPLE_ITERATOR_GET_ALL_CURRENT == get_type) { + zend_call_method_with_0_params(&it, Z_OBJCE_P(it), &Z_OBJCE_P(it)->iterator_funcs.zf_current, "current", &retval); + } else { + zend_call_method_with_0_params(&it, Z_OBJCE_P(it), &Z_OBJCE_P(it)->iterator_funcs.zf_key, "key", &retval); + } + if (!retval) { + zend_throw_exception(spl_ce_RuntimeException, "Failed to call sub iterator method", 0 TSRMLS_CC); + return; + } + } else if (intern->flags & MIT_NEED_ALL) { + if (SPL_MULTIPLE_ITERATOR_GET_ALL_CURRENT == get_type) { + zend_throw_exception(spl_ce_RuntimeException, "Called current() with non valid sub iterator", 0 TSRMLS_CC); + } else { + zend_throw_exception(spl_ce_RuntimeException, "Called key() with non valid sub iterator", 0 TSRMLS_CC); + } + return; + } else { + ALLOC_INIT_ZVAL(retval); + } + + if (intern->flags & MIT_KEYS_ASSOC) { + switch (Z_TYPE_P(element->inf)) { + case IS_LONG: + add_index_zval(return_value, Z_LVAL_P(element->inf), retval); + break; + case IS_STRING: + add_assoc_zval_ex(return_value, Z_STRVAL_P(element->inf), Z_STRLEN_P(element->inf)+1U, retval); + break; + default: + zval_ptr_dtor(&retval); + zend_throw_exception(spl_ce_InvalidArgumentException, "Sub-Iterator is associated with NULL", 0 TSRMLS_CC); + return; + } + } else { + add_next_index_zval(return_value, retval); + } + + zend_hash_move_forward_ex(&intern->storage, &intern->pos); + } +} +/* }}} */ + +/* {{{ proto array current() throws RuntimeException throws InvalidArgumentException + Return an array of all registered Iterator instances current() result */ +SPL_METHOD(MultipleIterator, current) +{ + spl_SplObjectStorage *intern; + intern = (spl_SplObjectStorage*)zend_object_store_get_object(getThis() TSRMLS_CC); + + spl_multiple_iterator_get_all(intern, SPL_MULTIPLE_ITERATOR_GET_ALL_CURRENT, return_value TSRMLS_CC); +} +/* }}} */ + +/* {{{ proto array MultipleIterator::key() + Return an array of all registered Iterator instances key() result */ +SPL_METHOD(MultipleIterator, key) +{ + spl_SplObjectStorage *intern; + intern = (spl_SplObjectStorage*)zend_object_store_get_object(getThis() TSRMLS_CC); + + spl_multiple_iterator_get_all(intern, SPL_MULTIPLE_ITERATOR_GET_ALL_KEY, return_value TSRMLS_CC); +} +/* }}} */ + +static +ZEND_BEGIN_ARG_INFO_EX(arginfo_MultipleIterator_attachIterator, 0, 0, 1) + ZEND_ARG_OBJ_INFO(0, iterator, Iterator, 0) + ZEND_ARG_INFO(0, infos) +ZEND_END_ARG_INFO(); + +static +ZEND_BEGIN_ARG_INFO_EX(arginfo_MultipleIterator_detachIterator, 0, 0, 1) + ZEND_ARG_OBJ_INFO(0, iterator, Iterator, 0) +ZEND_END_ARG_INFO(); + +static +ZEND_BEGIN_ARG_INFO_EX(arginfo_MultipleIterator_containsIterator, 0, 0, 1) + ZEND_ARG_OBJ_INFO(0, iterator, Iterator, 0) +ZEND_END_ARG_INFO(); + +static const zend_function_entry spl_funcs_MultipleIterator[] = { + SPL_ME(MultipleIterator, __construct, NULL, 0) + SPL_ME(MultipleIterator, getFlags, NULL, 0) + SPL_ME(MultipleIterator, setFlags, NULL, 0) + SPL_ME(MultipleIterator, attachIterator, arginfo_MultipleIterator_attachIterator, 0) + SPL_MA(MultipleIterator, detachIterator, SplObjectStorage, detach, arginfo_MultipleIterator_detachIterator, 0) + SPL_MA(MultipleIterator, containsIterator, SplObjectStorage, contains, arginfo_MultipleIterator_containsIterator, 0) + SPL_MA(MultipleIterator, countIterators, SplObjectStorage, count, NULL, 0) + /* Iterator */ + SPL_ME(MultipleIterator, rewind, NULL, 0) + SPL_ME(MultipleIterator, valid, NULL, 0) + SPL_ME(MultipleIterator, key, NULL, 0) + SPL_ME(MultipleIterator, current, NULL, 0) + SPL_ME(MultipleIterator, next, NULL, 0) + {NULL, NULL, NULL} +}; + /* {{{ PHP_MINIT_FUNCTION(spl_observer) */ PHP_MINIT_FUNCTION(spl_observer) { @@ -635,6 +929,14 @@ PHP_MINIT_FUNCTION(spl_observer) REGISTER_SPL_IMPLEMENTS(SplObjectStorage, Serializable); REGISTER_SPL_IMPLEMENTS(SplObjectStorage, ArrayAccess); + REGISTER_SPL_STD_CLASS_EX(MultipleIterator, spl_SplObjectStorage_new, spl_funcs_MultipleIterator); + REGISTER_SPL_ITERATOR(MultipleIterator); + + REGISTER_SPL_CLASS_CONST_LONG(MultipleIterator, "MIT_NEED_ANY", MIT_NEED_ANY); + REGISTER_SPL_CLASS_CONST_LONG(MultipleIterator, "MIT_NEED_ALL", MIT_NEED_ALL); + REGISTER_SPL_CLASS_CONST_LONG(MultipleIterator, "MIT_KEYS_NUMERIC", MIT_KEYS_NUMERIC); + REGISTER_SPL_CLASS_CONST_LONG(MultipleIterator, "MIT_KEYS_ASSOC", MIT_KEYS_ASSOC); + return SUCCESS; } /* }}} */ diff --git a/ext/spl/spl_observer.h b/ext/spl/spl_observer.h index e3ada6d301..23d0f2fd36 100755 --- a/ext/spl/spl_observer.h +++ b/ext/spl/spl_observer.h @@ -27,6 +27,7 @@ extern PHPAPI zend_class_entry *spl_ce_SplObserver; extern PHPAPI zend_class_entry *spl_ce_SplSubject; extern PHPAPI zend_class_entry *spl_ce_SplObjectStorage; +extern PHPAPI zend_class_entry *spl_ce_MultipleIterator; PHP_MINIT_FUNCTION(spl_observer); diff --git a/ext/spl/tests/multiple_iterator_001.phpt b/ext/spl/tests/multiple_iterator_001.phpt new file mode 100755 index 0000000000..edd03f5040 --- /dev/null +++ b/ext/spl/tests/multiple_iterator_001.phpt @@ -0,0 +1,345 @@ +--TEST-- +SPL: MultipleIterator +--FILE-- +current()); + +$m->attachIterator($iter1); +$m->attachIterator($iter2); +$m->attachIterator($iter3); + +echo "-- Default flags, MultipleIterator::MIT_NEED_ALL | MultipleIterator::MIT_KEYS_NUMERIC --\n"; + +var_dump($m->getFlags() === (MultipleIterator::MIT_NEED_ALL | MultipleIterator::MIT_KEYS_NUMERIC)); + +foreach($m as $value) { + var_dump($m->key(), $value); +} +try { + $m->current(); +} catch(RuntimeException $e) { + echo "RuntimeException thrown: " . $e->getMessage() . "\n"; +} +try { + $m->key(); +} catch(RuntimeException $e) { + echo "RuntimeException thrown: " . $e->getMessage() . "\n"; +} + +echo "-- Flags = MultipleIterator::MIT_NEED_ANY | MultipleIterator::MIT_KEYS_NUMERIC --\n"; + +$m->setFlags(MultipleIterator::MIT_NEED_ANY | MultipleIterator::MIT_KEYS_NUMERIC); +var_dump($m->getFlags() === (MultipleIterator::MIT_NEED_ANY | MultipleIterator::MIT_KEYS_NUMERIC)); + +foreach($m as $value) { + var_dump($m->key(), $value); +} + +echo "-- Default flags, added element --\n"; + +$m->setFlags(MultipleIterator::MIT_NEED_ALL | MultipleIterator::MIT_KEYS_NUMERIC); + +$iter2[] = 3; +foreach($m as $value) { + var_dump($m->key(), $value); +} + +echo "-- Flags |= MultipleIterator::MIT_KEYS_ASSOC, with iterator associated with NULL --\n"; + +$m->setFlags(MultipleIterator::MIT_NEED_ALL | MultipleIterator::MIT_KEYS_ASSOC); +$m->rewind(); +try { + $m->current(); +} catch(InvalidArgumentException $e) { + echo "InvalidArgumentException thrown: " . $e->getMessage() . "\n"; +} + +echo "-- Flags |= MultipleIterator::MIT_KEYS_ASSOC --\n"; + +$m->attachIterator($iter1, "iter1"); +$m->attachIterator($iter2, b"iter2"); +$m->attachIterator($iter3, 3); + +foreach($m as $value) { + var_dump($m->key(), $value); +} + +echo "-- Associate with invalid value --\n"; + +try { + $m->attachIterator($iter3, new stdClass()); +} catch(InvalidArgumentException $e) { + echo "InvalidArgumentException thrown: " . $e->getMessage() . "\n"; +} + +echo "-- Associate with duplicate value --\n"; + +try { + $m->attachIterator($iter3, "iter1"); +} catch(InvalidArgumentException $e) { + echo "InvalidArgumentException thrown: " . $e->getMessage() . "\n"; +} + +echo "-- Count, contains, detach, count, contains, iterate --\n"; + +var_dump($m->countIterators()); +var_dump($m->containsIterator($iter2)); +var_dump($m->detachIterator($iter2)); +var_dump($m->countIterators()); +var_dump($m->containsIterator($iter2)); +foreach($m as $value) { + var_dump($m->key(), $value); +} + +?> +--EXPECTF-- +-- Default flags, no iterators -- +bool(false) +-- Default flags, MultipleIterator::MIT_NEED_ALL | MultipleIterator::MIT_KEYS_NUMERIC -- +bool(true) +array(3) { + [0]=> + int(0) + [1]=> + int(0) + [2]=> + int(0) +} +array(3) { + [0]=> + int(1) + [1]=> + int(1) + [2]=> + object(stdClass)#%d (0) { + } +} +array(3) { + [0]=> + int(1) + [1]=> + int(1) + [2]=> + int(1) +} +array(3) { + [0]=> + int(2) + [1]=> + int(2) + [2]=> + string(6) "string" +} +RuntimeException thrown: Called current() with non valid sub iterator +RuntimeException thrown: Called key() with non valid sub iterator +-- Flags = MultipleIterator::MIT_NEED_ANY | MultipleIterator::MIT_KEYS_NUMERIC -- +bool(true) +array(3) { + [0]=> + int(0) + [1]=> + int(0) + [2]=> + int(0) +} +array(3) { + [0]=> + int(1) + [1]=> + int(1) + [2]=> + object(stdClass)#%d (0) { + } +} +array(3) { + [0]=> + int(1) + [1]=> + int(1) + [2]=> + int(1) +} +array(3) { + [0]=> + int(2) + [1]=> + int(2) + [2]=> + string(6) "string" +} +array(3) { + [0]=> + int(2) + [1]=> + NULL + [2]=> + int(2) +} +array(3) { + [0]=> + int(3) + [1]=> + NULL + [2]=> + int(3) +} +-- Default flags, added element -- +array(3) { + [0]=> + int(0) + [1]=> + int(0) + [2]=> + int(0) +} +array(3) { + [0]=> + int(1) + [1]=> + int(1) + [2]=> + object(stdClass)#%d (0) { + } +} +array(3) { + [0]=> + int(1) + [1]=> + int(1) + [2]=> + int(1) +} +array(3) { + [0]=> + int(2) + [1]=> + int(2) + [2]=> + string(6) "string" +} +array(3) { + [0]=> + int(2) + [1]=> + int(2) + [2]=> + int(2) +} +array(3) { + [0]=> + int(3) + [1]=> + int(3) + [2]=> + int(3) +} +-- Flags |= MultipleIterator::MIT_KEYS_ASSOC, with iterator associated with NULL -- +InvalidArgumentException thrown: Sub-Iterator is associated with NULL +-- Flags |= MultipleIterator::MIT_KEYS_ASSOC -- +array(3) { + ["iter1"]=> + int(0) + ["iter2"]=> + int(0) + [3]=> + int(0) +} +array(3) { + ["iter1"]=> + int(1) + ["iter2"]=> + int(1) + [3]=> + object(stdClass)#%d (0) { + } +} +array(3) { + ["iter1"]=> + int(1) + ["iter2"]=> + int(1) + [3]=> + int(1) +} +array(3) { + ["iter1"]=> + int(2) + ["iter2"]=> + int(2) + [3]=> + string(6) "string" +} +array(3) { + ["iter1"]=> + int(2) + ["iter2"]=> + int(2) + [3]=> + int(2) +} +array(3) { + ["iter1"]=> + int(3) + ["iter2"]=> + int(3) + [3]=> + int(3) +} +-- Associate with invalid value -- +InvalidArgumentException thrown: Info must be NULL, integer or string +-- Associate with duplicate value -- +InvalidArgumentException thrown: Key duplication error +-- Count, contains, detach, count, contains, iterate -- +int(3) +bool(true) +NULL +int(2) +bool(false) +array(2) { + ["iter1"]=> + int(0) + [3]=> + int(0) +} +array(2) { + ["iter1"]=> + int(1) + [3]=> + object(stdClass)#%d (0) { + } +} +array(2) { + ["iter1"]=> + int(1) + [3]=> + int(1) +} +array(2) { + ["iter1"]=> + int(2) + [3]=> + string(6) "string" +} +array(2) { + ["iter1"]=> + int(2) + [3]=> + int(2) +} +array(2) { + ["iter1"]=> + int(3) + [3]=> + int(3) +} -- 2.40.0