From b83e243c23daafdecaec75461b5ff4705733608d Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Sun, 27 Mar 2011 20:13:27 +0000 Subject: [PATCH] Added CallbackFilterIterator and RecursiveCallbackFilterIterator classes [DOC] This is a concrete FilterIterator which takes a callback as constructor parameter, and uses this callback for filtering. This allows to use FilterIterator without extending it. CallbackFilterIterator Example: $it = new ArrayIterator(range(1,100)); $it = new CallbackFilterIterator($it, function($value) { return $value % 2; }); foreach($it as $value) // ... RecursiveCallbackFilterIterator Example: $it = new RecursiveDirectoryIterator("/"); $it = new RecursiveCallbackFilterIterator($it, function($file, $k, $it) { return $it->hasChildren() || $file->getSize() > 1024; }); foreach(new RecursiveIteratorIterator($it) as $file) // ... The callback takes the current value, the current key and the inner iterator as parameters. --- NEWS | 1 + ext/spl/spl_iterators.c | 143 ++++++++++++++++++ ext/spl/spl_iterators.h | 8 + .../tests/CallbackFilterIteratorTest-002.phpt | 71 +++++++++ ext/spl/tests/CallbackFilterIteratorTest.phpt | 133 ++++++++++++++++ .../RecursiveCallbackFilterIteratorTest.phpt | 138 +++++++++++++++++ 6 files changed, 494 insertions(+) create mode 100644 ext/spl/tests/CallbackFilterIteratorTest-002.phpt create mode 100644 ext/spl/tests/CallbackFilterIteratorTest.phpt create mode 100644 ext/spl/tests/RecursiveCallbackFilterIteratorTest.phpt diff --git a/NEWS b/NEWS index 7d3dc75c74..382b4d7294 100644 --- a/NEWS +++ b/NEWS @@ -197,6 +197,7 @@ PHP NEWS . Added RegexIterator::getRegex() method. (Joshua Thijssen) . Added SplObjectStorage::getHash() hook. (Etienne) . Added SplFileInfo::getExtension(). FR #48767. (Peter Cowburn) + . Added CallbackFilterIterator and RecursiveCallbackFilterIterator (Arnaud) - Improved ZLIB extension: . Re-implemented non-file related functionality. (Mike) diff --git a/ext/spl/spl_iterators.c b/ext/spl/spl_iterators.c index 1e2c44359c..d8ee7c525f 100755 --- a/ext/spl/spl_iterators.c +++ b/ext/spl/spl_iterators.c @@ -44,7 +44,9 @@ PHPAPI zend_class_entry *spl_ce_RecursiveIterator; PHPAPI zend_class_entry *spl_ce_RecursiveIteratorIterator; PHPAPI zend_class_entry *spl_ce_FilterIterator; +PHPAPI zend_class_entry *spl_ce_CallbackFilterIterator; PHPAPI zend_class_entry *spl_ce_RecursiveFilterIterator; +PHPAPI zend_class_entry *spl_ce_RecursiveCallbackFilterIterator; PHPAPI zend_class_entry *spl_ce_ParentIterator; PHPAPI zend_class_entry *spl_ce_SeekableIterator; PHPAPI zend_class_entry *spl_ce_LimitIterator; @@ -1499,6 +1501,23 @@ static spl_dual_it_object* spl_dual_it_construct(INTERNAL_FUNCTION_PARAMETERS, z break; } #endif + case DIT_CallbackFilterIterator: + case DIT_RecursiveCallbackFilterIterator: { + _spl_cbfilter_it_intern *cfi = emalloc(sizeof(*cfi)); + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Of", &zobject, ce_inner, &cfi->fci, &cfi->fcc) == FAILURE) { + zend_restore_error_handling(&error_handling TSRMLS_CC); + efree(cfi); + return NULL; + } + if (cfi->fci.function_name) { + Z_ADDREF_P(cfi->fci.function_name); + } + if (cfi->fci.object_ptr) { + Z_ADDREF_P(cfi->fci.object_ptr); + } + intern->u.cbfilter = cfi; + break; + } default: if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &zobject, ce_inner) == FAILURE) { zend_restore_error_handling(&error_handling TSRMLS_CC); @@ -1527,6 +1546,13 @@ SPL_METHOD(FilterIterator, __construct) spl_dual_it_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, spl_ce_FilterIterator, zend_ce_iterator, DIT_FilterIterator); } /* }}} */ +/* {{{ proto void CallbackFilterIterator::__construct(Iterator it, callback) + Create an Iterator from another iterator */ +SPL_METHOD(CallbackFilterIterator, __construct) +{ + spl_dual_it_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, spl_ce_CallbackFilterIterator, zend_ce_iterator, DIT_CallbackFilterIterator); +} /* }}} */ + /* {{{ proto Iterator FilterIterator::getInnerIterator() proto Iterator CachingIterator::getInnerIterator() proto Iterator LimitIterator::getInnerIterator() @@ -1800,6 +1826,14 @@ SPL_METHOD(FilterIterator, next) spl_filter_it_next(getThis(), intern TSRMLS_CC); } /* }}} */ +/* {{{ proto void RecursiveCallbackFilterIterator::__construct(RecursiveIterator it, callback) + Create a RecursiveCallbackFilterIterator from a RecursiveIterator */ +SPL_METHOD(RecursiveCallbackFilterIterator, __construct) +{ + spl_dual_it_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, spl_ce_RecursiveCallbackFilterIterator, spl_ce_RecursiveIterator, DIT_RecursiveCallbackFilterIterator); +} /* }}} */ + + /* {{{ proto void RecursiveFilterIterator::__construct(RecursiveIterator it) Create a RecursiveFilterIterator from a RecursiveIterator */ SPL_METHOD(RecursiveFilterIterator, __construct) @@ -1850,6 +1884,27 @@ SPL_METHOD(RecursiveFilterIterator, getChildren) } } /* }}} */ +/* {{{ proto RecursiveCallbackFilterIterator RecursiveCallbackFilterIterator::getChildren() + Return the inner iterator's children contained in a RecursiveCallbackFilterIterator */ +SPL_METHOD(RecursiveCallbackFilterIterator, getChildren) +{ + spl_dual_it_object *intern; + zval *retval; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + intern = (spl_dual_it_object*)zend_object_store_get_object(getThis() TSRMLS_CC); + + zend_call_method_with_0_params(&intern->inner.zobject, intern->inner.ce, NULL, "getchildren", &retval); + if (!EG(exception) && retval) { + spl_instantiate_arg_ex2(Z_OBJCE_P(getThis()), &return_value, 0, retval, intern->u.cbfilter->fci.function_name TSRMLS_CC); + } + if (retval) { + zval_ptr_dtor(&retval); + } +} /* }}} */ /* {{{ proto void ParentIterator::__construct(RecursiveIterator it) Create a ParentIterator from a RecursiveIterator */ SPL_METHOD(ParentIterator, __construct) @@ -1865,6 +1920,53 @@ SPL_METHOD(RegexIterator, __construct) spl_dual_it_construct(INTERNAL_FUNCTION_PARAM_PASSTHRU, spl_ce_RegexIterator, zend_ce_iterator, DIT_RegexIterator); } /* }}} */ +/* {{{ proto bool CallbackFilterIterator::accept() + Calls the callback with the current value, the current key and the inner iterator as arguments */ +SPL_METHOD(CallbackFilterIterator, accept) +{ + spl_dual_it_object *intern = (spl_dual_it_object*)zend_object_store_get_object(getThis() TSRMLS_CC); + zend_fcall_info *fci = &intern->u.cbfilter->fci; + zend_fcall_info_cache *fcc = &intern->u.cbfilter->fcc; + zval **params[3]; + zval zkey; + zval *zkey_p = &zkey; + zval *result; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + if (intern->current.data == NULL) { + RETURN_FALSE; + } + + INIT_PZVAL(&zkey); + if (intern->current.key_type == HASH_KEY_IS_LONG) { + ZVAL_LONG(&zkey, intern->current.int_key); + } else { + ZVAL_STRINGL(&zkey, intern->current.str_key, intern->current.str_key_len-1, 0); + } + + params[0] = &intern->current.data; + params[1] = &zkey_p; + params[2] = &intern->inner.zobject; + + fci->retval_ptr_ptr = &result; + fci->param_count = 3; + fci->params = params; + fci->no_separation = 0; + + if (zend_call_function(fci, fcc TSRMLS_CC) != SUCCESS || !result) { + RETURN_FALSE; + } + if (EG(exception)) { + return; + } + + RETURN_ZVAL(result, 1, 1); +} +/* }}} */ + /* {{{ proto bool RegexIterator::accept() Match (string)current() against regular expression */ SPL_METHOD(RegexIterator, accept) @@ -2154,6 +2256,18 @@ static void spl_dual_it_free_storage(void *_object TSRMLS_DC) } #endif + if (object->dit_type == DIT_CallbackFilterIterator || object->dit_type == DIT_RecursiveCallbackFilterIterator) { + if (object->u.cbfilter) { + if (object->u.cbfilter->fci.function_name) { + zval_ptr_dtor(&object->u.cbfilter->fci.function_name); + } + if (object->u.cbfilter->fci.object_ptr) { + zval_ptr_dtor(&object->u.cbfilter->fci.object_ptr); + } + efree(object->u.cbfilter); + } + } + zend_object_std_dtor(&object->std TSRMLS_CC); efree(object); @@ -2195,6 +2309,29 @@ static const zend_function_entry spl_funcs_FilterIterator[] = { {NULL, NULL, NULL} }; +ZEND_BEGIN_ARG_INFO(arginfo_callback_filter_it___construct, 0) + ZEND_ARG_OBJ_INFO(0, iterator, Iterator, 0) + ZEND_ARG_INFO(0, callback) +ZEND_END_ARG_INFO(); + +static const zend_function_entry spl_funcs_CallbackFilterIterator[] = { + SPL_ME(CallbackFilterIterator, __construct, arginfo_callback_filter_it___construct, ZEND_ACC_PUBLIC) + SPL_ME(CallbackFilterIterator, accept, arginfo_recursive_it_void, ZEND_ACC_PUBLIC) + {NULL, NULL, NULL} +}; + +ZEND_BEGIN_ARG_INFO(arginfo_recursive_callback_filter_it___construct, 0) + ZEND_ARG_OBJ_INFO(0, iterator, RecursiveIterator, 0) + ZEND_ARG_INFO(0, callback) +ZEND_END_ARG_INFO(); + +static const zend_function_entry spl_funcs_RecursiveCallbackFilterIterator[] = { + SPL_ME(RecursiveCallbackFilterIterator, __construct, arginfo_recursive_callback_filter_it___construct, ZEND_ACC_PUBLIC) + SPL_ME(RecursiveFilterIterator, hasChildren, arginfo_recursive_it_void, ZEND_ACC_PUBLIC) + SPL_ME(RecursiveCallbackFilterIterator, getChildren, arginfo_recursive_it_void, ZEND_ACC_PUBLIC) + {NULL, NULL, NULL} +}; + ZEND_BEGIN_ARG_INFO(arginfo_parent_it___construct, 0) ZEND_ARG_OBJ_INFO(0, iterator, RecursiveIterator, 0) ZEND_END_ARG_INFO(); @@ -3535,6 +3672,12 @@ PHP_MINIT_FUNCTION(spl_iterators) REGISTER_SPL_SUB_CLASS_EX(RecursiveFilterIterator, FilterIterator, spl_dual_it_new, spl_funcs_RecursiveFilterIterator); REGISTER_SPL_IMPLEMENTS(RecursiveFilterIterator, RecursiveIterator); + REGISTER_SPL_SUB_CLASS_EX(CallbackFilterIterator, FilterIterator, spl_dual_it_new, spl_funcs_CallbackFilterIterator); + + REGISTER_SPL_SUB_CLASS_EX(RecursiveCallbackFilterIterator, CallbackFilterIterator, spl_dual_it_new, spl_funcs_RecursiveCallbackFilterIterator); + REGISTER_SPL_IMPLEMENTS(RecursiveCallbackFilterIterator, RecursiveIterator); + + REGISTER_SPL_SUB_CLASS_EX(ParentIterator, RecursiveFilterIterator, spl_dual_it_new, spl_funcs_ParentIterator); REGISTER_SPL_INTERFACE(Countable); diff --git a/ext/spl/spl_iterators.h b/ext/spl/spl_iterators.h index ef59fcc6ea..9bbdd68751 100755 --- a/ext/spl/spl_iterators.h +++ b/ext/spl/spl_iterators.h @@ -75,6 +75,8 @@ typedef enum { DIT_RegexIterator, DIT_RecursiveRegexIterator, #endif + DIT_CallbackFilterIterator, + DIT_RecursiveCallbackFilterIterator, DIT_Unknown = ~0 } dual_it_type; @@ -114,6 +116,11 @@ typedef enum { REGIT_MODE_MAX } regex_mode; +typedef struct _spl_cbfilter_it_intern { + zend_fcall_info fci; + zend_fcall_info_cache fcc; +} _spl_cbfilter_it_intern; + typedef struct _spl_dual_it_object { zend_object std; struct { @@ -157,6 +164,7 @@ typedef struct _spl_dual_it_object { uint regex_len; } regex; #endif + _spl_cbfilter_it_intern *cbfilter; } u; } spl_dual_it_object; diff --git a/ext/spl/tests/CallbackFilterIteratorTest-002.phpt b/ext/spl/tests/CallbackFilterIteratorTest-002.phpt new file mode 100644 index 0000000000..4b03d958ba --- /dev/null +++ b/ext/spl/tests/CallbackFilterIteratorTest-002.phpt @@ -0,0 +1,71 @@ +--TEST-- +CallbackFilterIterator 002 +--FILE-- +getMessage() . "\n"; +} + +try { + new CallbackFilterIterator(null); +} catch(InvalidArgumentException $e) { + echo $e->getMessage() . "\n"; +} + +try { + new CallbackFilterIterator(new ArrayIterator(array()), null); +} catch(InvalidArgumentException $e) { + echo $e->getMessage() . "\n"; +} + +try { + new CallbackFilterIterator(new ArrayIterator(array()), array()); +} catch(InvalidArgumentException $e) { + echo $e->getMessage() . "\n"; +} + +$it = new CallbackFilterIterator(new ArrayIterator(array(1)), function() { + throw new Exception("some message"); +}); +try { + foreach($it as $e); +} catch(Exception $e) { + echo $e->getMessage() . "\n"; +} + +class Test extends CallbackFilterIterator { + function __construct(){} +} +class Test2 extends RecursiveCallbackFilterIterator { + function __construct(){} +} + +try { + new Test; +} catch(LogicException $e) { + echo $e->getMessage() . "\n"; +} + +try { + new Test2; +} catch(LogicException $e) { + echo $e->getMessage() . "\n"; +} + +--EXPECT-- +CallbackFilterIterator::__construct() expects exactly 2 parameters, 0 given +Argument 1 passed to CallbackFilterIterator::__construct() must implement interface Iterator, null given +CallbackFilterIterator::__construct() expects exactly 2 parameters, 1 given +CallbackFilterIterator::__construct() expects parameter 2 to be a valid callback, no array or string given +CallbackFilterIterator::__construct() expects parameter 2 to be a valid callback, array must have exactly two members +some message +In the constructor of Test, parent::__construct() must be called and its exceptions cannot be cleared +In the constructor of Test2, parent::__construct() must be called and its exceptions cannot be cleared diff --git a/ext/spl/tests/CallbackFilterIteratorTest.phpt b/ext/spl/tests/CallbackFilterIteratorTest.phpt new file mode 100644 index 0000000000..36bc770a95 --- /dev/null +++ b/ext/spl/tests/CallbackFilterIteratorTest.phpt @@ -0,0 +1,133 @@ +--TEST-- +CallbackFilterIterator +--FILE-- +current() + , $key == $inner->key() + ); + return $value === 1 || $value === 4; +} + +$tests = array( + 'instance method' => function() { return array(new A, 'test'); }, + 'static method' => function() { return array('B', 'test'); }, + 'static method (2)' => function() { return 'B::test'; }, + 'function' => function() { return 'test'; }, + 'anonymous function' => function() { return function($value, $key, $inner) { return test($value, $key, $inner); }; }, +); + +foreach($tests as $name => $test) { + + $callback = $test(); + $it = new ArrayIterator(range(1, 5)); + $it = new CallbackFilterIterator($it, $callback); + + echo " = $name =\n"; + + foreach($it as $value) { + echo "=> $value\n"; + } + + // same test, with no reference to callback + + $it = new ArrayIterator(range(1, 5)); + $it = new CallbackFilterIterator($it, $test()); + unset($callback); + + foreach($it as $value) { + echo "=> $value\n"; + } +} +--EXPECT-- += instance method = +1 / 0 / 1 / 1 +=> 1 +2 / 1 / 1 / 1 +3 / 2 / 1 / 1 +4 / 3 / 1 / 1 +=> 4 +5 / 4 / 1 / 1 +1 / 0 / 1 / 1 +=> 1 +2 / 1 / 1 / 1 +3 / 2 / 1 / 1 +4 / 3 / 1 / 1 +=> 4 +5 / 4 / 1 / 1 + = static method = +1 / 0 / 1 / 1 +=> 1 +2 / 1 / 1 / 1 +3 / 2 / 1 / 1 +4 / 3 / 1 / 1 +=> 4 +5 / 4 / 1 / 1 +1 / 0 / 1 / 1 +=> 1 +2 / 1 / 1 / 1 +3 / 2 / 1 / 1 +4 / 3 / 1 / 1 +=> 4 +5 / 4 / 1 / 1 + = static method (2) = +1 / 0 / 1 / 1 +=> 1 +2 / 1 / 1 / 1 +3 / 2 / 1 / 1 +4 / 3 / 1 / 1 +=> 4 +5 / 4 / 1 / 1 +1 / 0 / 1 / 1 +=> 1 +2 / 1 / 1 / 1 +3 / 2 / 1 / 1 +4 / 3 / 1 / 1 +=> 4 +5 / 4 / 1 / 1 + = function = +1 / 0 / 1 / 1 +=> 1 +2 / 1 / 1 / 1 +3 / 2 / 1 / 1 +4 / 3 / 1 / 1 +=> 4 +5 / 4 / 1 / 1 +1 / 0 / 1 / 1 +=> 1 +2 / 1 / 1 / 1 +3 / 2 / 1 / 1 +4 / 3 / 1 / 1 +=> 4 +5 / 4 / 1 / 1 + = anonymous function = +1 / 0 / 1 / 1 +=> 1 +2 / 1 / 1 / 1 +3 / 2 / 1 / 1 +4 / 3 / 1 / 1 +=> 4 +5 / 4 / 1 / 1 +1 / 0 / 1 / 1 +=> 1 +2 / 1 / 1 / 1 +3 / 2 / 1 / 1 +4 / 3 / 1 / 1 +=> 4 +5 / 4 / 1 / 1 diff --git a/ext/spl/tests/RecursiveCallbackFilterIteratorTest.phpt b/ext/spl/tests/RecursiveCallbackFilterIteratorTest.phpt new file mode 100644 index 0000000000..f55afd8202 --- /dev/null +++ b/ext/spl/tests/RecursiveCallbackFilterIteratorTest.phpt @@ -0,0 +1,138 @@ +--TEST-- +RecursiveCallbackFilterIterator +--FILE-- +hasChildren()) { + return true; + } + printf("%s / %s / %d / %d\n" + , print_r($value, true) + , $key + , $value == $inner->current() + , $key == $inner->key() + ); + return $value === 1 || $value === 4; +} + +$tests = array( + 'instance method' => function() { return array(new A, 'test'); }, + 'static method' => function() { return array('B', 'test'); }, + 'static method (2)' => function() { return 'B::test'; }, + 'function' => function() { return 'test'; }, + 'anonymous function' => function() { return function($value, $key, $inner) { return test($value, $key, $inner); }; }, +); + +foreach($tests as $name => $test) { + + $callback = $test(); + $it = new RecursiveArrayIterator(array(1, array(2, 3), array(4, 5))); + $it = new RecursiveCallbackFilterIterator($it, $callback); + $it = new RecursiveIteratorIterator($it); + + echo " = $name =\n"; + + foreach($it as $value) { + echo "=> $value\n"; + } + + // same test, with no reference to callback + + $it = new RecursiveArrayIterator(array(1, array(2, 3), array(4, 5))); + $it = new RecursiveCallbackFilterIterator($it, $test()); + $it = new RecursiveIteratorIterator($it); + unset($callback); + + foreach($it as $value) { + echo "=> $value\n"; + } +} +--EXPECT-- += instance method = +1 / 0 / 1 / 1 +=> 1 +2 / 0 / 1 / 1 +3 / 1 / 1 / 1 +4 / 0 / 1 / 1 +=> 4 +5 / 1 / 1 / 1 +1 / 0 / 1 / 1 +=> 1 +2 / 0 / 1 / 1 +3 / 1 / 1 / 1 +4 / 0 / 1 / 1 +=> 4 +5 / 1 / 1 / 1 + = static method = +1 / 0 / 1 / 1 +=> 1 +2 / 0 / 1 / 1 +3 / 1 / 1 / 1 +4 / 0 / 1 / 1 +=> 4 +5 / 1 / 1 / 1 +1 / 0 / 1 / 1 +=> 1 +2 / 0 / 1 / 1 +3 / 1 / 1 / 1 +4 / 0 / 1 / 1 +=> 4 +5 / 1 / 1 / 1 + = static method (2) = +1 / 0 / 1 / 1 +=> 1 +2 / 0 / 1 / 1 +3 / 1 / 1 / 1 +4 / 0 / 1 / 1 +=> 4 +5 / 1 / 1 / 1 +1 / 0 / 1 / 1 +=> 1 +2 / 0 / 1 / 1 +3 / 1 / 1 / 1 +4 / 0 / 1 / 1 +=> 4 +5 / 1 / 1 / 1 + = function = +1 / 0 / 1 / 1 +=> 1 +2 / 0 / 1 / 1 +3 / 1 / 1 / 1 +4 / 0 / 1 / 1 +=> 4 +5 / 1 / 1 / 1 +1 / 0 / 1 / 1 +=> 1 +2 / 0 / 1 / 1 +3 / 1 / 1 / 1 +4 / 0 / 1 / 1 +=> 4 +5 / 1 / 1 / 1 + = anonymous function = +1 / 0 / 1 / 1 +=> 1 +2 / 0 / 1 / 1 +3 / 1 / 1 / 1 +4 / 0 / 1 / 1 +=> 4 +5 / 1 / 1 / 1 +1 / 0 / 1 / 1 +=> 1 +2 / 0 / 1 / 1 +3 / 1 / 1 / 1 +4 / 0 / 1 / 1 +=> 4 +5 / 1 / 1 / 1 -- 2.40.0