From f9de3fd02b236b03d607979e8b1e98a26f90ae44 Mon Sep 17 00:00:00 2001 From: Marcus Boerger Date: Sat, 12 Mar 2005 23:03:33 +0000 Subject: [PATCH] - Allow to hook into RecursiveIteratorIterator's child handling - Better=faster function caching for RecursiveIteratorIterator - Check for exceptions and fix check for illegal return values - Add UnexpectedValueException - Add docu --- ext/spl/spl.php | 23 +++- ext/spl/spl_exceptions.c | 2 + ext/spl/spl_exceptions.h | 1 + ext/spl/spl_iterators.c | 84 ++++++++++++-- ext/spl/tests/iterator_021.phpt | 185 +++++++++++++++++++++++++++++++ ext/spl/tests/iterator_022.phpt | 191 ++++++++++++++++++++++++++++++++ 6 files changed, 473 insertions(+), 13 deletions(-) create mode 100755 ext/spl/tests/iterator_021.phpt create mode 100755 ext/spl/tests/iterator_022.phpt diff --git a/ext/spl/spl.php b/ext/spl/spl.php index cac58446e7..b4c7ddfc2c 100755 --- a/ext/spl/spl.php +++ b/ext/spl/spl.php @@ -252,6 +252,8 @@ class BadMethodCallException extends BadFunctionCallException * * This kind of exception should be used to inform about domain erors in * mathematical sense. + * + * @see RangeException */ class DomainException extends LogicException { @@ -260,6 +262,8 @@ class DomainException extends LogicException /** @ingroup SPL * @brief Exception that denotes invalid arguments were passed. * @since PHP 5.1 + * + * @see UnexpectedValueException */ class InvalidArgumentException extends LogicException { @@ -321,19 +325,36 @@ class OverflowException extends RuntimeException * @since PHP 5.1 * * Normally this means there was an arithmetic error other than under/overflow. + * This is the runtime version of DomainException. + * + * @see DomainException */ class RangeException extends RuntimeException { } /** @ingroup SPL - * @brief Exception Exception thrown to indicate arithmetic/buffer underflow. + * @brief Exception thrown to indicate arithmetic/buffer underflow. * @since PHP 5.1 */ class UnderflowException extends RuntimeException { } +/** @ingroup SPL + * @brief Exception thrown to indicate an unexpected value. + * @since PHP 5.1 + * + * Typically this happens when a function calls another function and espects + * the return value to be of a certain type or value not including arithmetic + * or buffer related errors. + * + * @see InvalidArgumentException + */ +class UnexpectedValueException extends RuntimeException +{ +} + /** @ingroup ZendEngine * @brief Interface to override array access of objects. * @since PHP 5.0 diff --git a/ext/spl/spl_exceptions.c b/ext/spl/spl_exceptions.c index c4f3a6b46f..8d0bbfa060 100755 --- a/ext/spl/spl_exceptions.c +++ b/ext/spl/spl_exceptions.c @@ -45,6 +45,7 @@ PHPAPI zend_class_entry *spl_ce_OutOfBoundsException; PHPAPI zend_class_entry *spl_ce_OverflowException; PHPAPI zend_class_entry *spl_ce_RangeException; PHPAPI zend_class_entry *spl_ce_UnderflowException; +PHPAPI zend_class_entry *spl_ce_UnexpectedValueException; #define spl_ce_Exception zend_exception_get_default() @@ -64,6 +65,7 @@ PHP_MINIT_FUNCTION(spl_exceptions) REGISTER_SPL_SUB_CLASS_EX(OverflowException, RuntimeException, NULL, NULL); REGISTER_SPL_SUB_CLASS_EX(RangeException, RuntimeException, NULL, NULL); REGISTER_SPL_SUB_CLASS_EX(UnderflowException, RuntimeException, NULL, NULL); + REGISTER_SPL_SUB_CLASS_EX(UnexpectedValueException, RuntimeException, NULL, NULL); return SUCCESS; } diff --git a/ext/spl/spl_exceptions.h b/ext/spl/spl_exceptions.h index 0f26e677b7..bb3e797092 100755 --- a/ext/spl/spl_exceptions.h +++ b/ext/spl/spl_exceptions.h @@ -37,6 +37,7 @@ extern PHPAPI zend_class_entry *spl_ce_OutOfBoundsException; extern PHPAPI zend_class_entry *spl_ce_OverflowException; extern PHPAPI zend_class_entry *spl_ce_RangeException; extern PHPAPI zend_class_entry *spl_ce_UnderflowException; +extern PHPAPI zend_class_entry *spl_ce_UnexpectedValueException; PHP_MINIT_FUNCTION(spl_exceptions); diff --git a/ext/spl/spl_iterators.c b/ext/spl/spl_iterators.c index bdf8d247d3..e9858ea774 100755 --- a/ext/spl/spl_iterators.c +++ b/ext/spl/spl_iterators.c @@ -85,6 +85,8 @@ typedef struct _spl_recursive_it_object { spl_sub_iterator *iterators; int level; RecursiveIteratorMode mode; + zend_function *callHasChildren; + zend_function *callGetChildren; zend_function *beginChildren; zend_function *endChildren; zend_class_entry *ce; @@ -183,7 +185,11 @@ next_step: case RS_TEST: ce = object->iterators[object->level].ce; zobject = object->iterators[object->level].zobject; - zend_call_method_with_0_params(&zobject, ce, NULL, "haschildren", &retval); + if (object->callHasChildren) { + zend_call_method_with_0_params(&zthis, object->ce, &object->callHasChildren, "callHasChildren", &retval); + } else { + zend_call_method_with_0_params(&zobject, ce, NULL, "haschildren", &retval); + } if (retval) { has_children = zend_is_true(retval); zval_ptr_dtor(&retval); @@ -211,13 +217,17 @@ next_step: case RS_CHILD: ce = object->iterators[object->level].ce; zobject = object->iterators[object->level].zobject; - zend_call_method_with_0_params(&zobject, ce, NULL, "getchildren", &child); - ce = child ? Z_OBJCE_P(child) : NULL; + if (object->callGetChildren) { + zend_call_method_with_0_params(&zthis, object->ce, &object->callGetChildren, "callGetChildren", &child); + } else { + zend_call_method_with_0_params(&zobject, ce, NULL, "getchildren", &child); + } + ce = child && Z_TYPE_P(child) == IS_OBJECT ? Z_OBJCE_P(child) : NULL; if (!ce || !instanceof_function(ce, spl_ce_RecursiveIterator TSRMLS_CC)) { if (child) { zval_ptr_dtor(&child); } - zend_throw_exception(spl_ce_InvalidArgumentException, "Objects returned by RecursiveIterator::getChildren() must implement RecursiveIterator", 0 TSRMLS_CC); + zend_throw_exception(spl_ce_UnexpectedValueException, "Objects returned by RecursiveIterator::getChildren() must implement RecursiveIterator", 0 TSRMLS_CC); return; } if (object->mode == RIT_CHILD_FIRST) { @@ -234,7 +244,7 @@ next_step: if (sub_iter->funcs->rewind) { sub_iter->funcs->rewind(sub_iter TSRMLS_CC); } - if (!object->beginChildren || object->beginChildren->common.scope != spl_ce_RecursiveIteratorIterator) { + if (object->beginChildren) { zend_call_method_with_0_params(&zthis, object->ce, &object->beginChildren, "beginchildren", NULL); } goto next_step; @@ -244,7 +254,7 @@ next_step: iterator->funcs->dtor(iterator TSRMLS_CC); zval_ptr_dtor(&object->iterators[object->level].zobject); object->level--; - if (!object->endChildren || object->endChildren->common.scope != spl_ce_RecursiveIteratorIterator) { + if (object->endChildren) { zend_call_method_with_0_params(&zthis, object->ce, &object->endChildren, "endchildren", NULL); } } else { @@ -335,9 +345,23 @@ SPL_METHOD(RecursiveIteratorIterator, __construct) intern->iterators = emalloc(sizeof(spl_sub_iterator)); intern->level = 0; intern->mode = mode; - intern->beginChildren = NULL; - intern->endChildren = NULL; intern->ce = Z_OBJCE_P(object); + zend_hash_find(&intern->ce->function_table, "callhaschildren", sizeof("callHasChildren"), (void **) &intern->callHasChildren); + if (intern->callHasChildren->common.scope == spl_ce_RecursiveIteratorIterator) { + intern->callHasChildren = NULL; + } + zend_hash_find(&intern->ce->function_table, "callgetchildren", sizeof("callGetChildren"), (void **) &intern->callGetChildren); + if (intern->callGetChildren->common.scope == spl_ce_RecursiveIteratorIterator) { + intern->callGetChildren = NULL; + } + zend_hash_find(&intern->ce->function_table, "beginchildren", sizeof("beginchildren"), (void **) &intern->beginChildren); + if (intern->beginChildren->common.scope == spl_ce_RecursiveIteratorIterator) { + intern->beginChildren = NULL; + } + zend_hash_find(&intern->ce->function_table, "endchildren", sizeof("endchildren"), (void **) &intern->endChildren); + if (intern->endChildren->common.scope == spl_ce_RecursiveIteratorIterator) { + intern->endChildren = NULL; + } ce_iterator = Z_OBJCE_P(iterator); /* respect inheritance, don't use spl_ce_RecursiveIterator */ intern->iterators[0].iterator = ce_iterator->get_iterator(ce_iterator, iterator TSRMLS_CC); iterator->refcount++; @@ -443,6 +467,40 @@ SPL_METHOD(RecursiveIteratorIterator, getInnerIterator) RETURN_ZVAL(object->iterators[level].zobject, 1, 0); } /* }}} */ +/* {{{ proto bool RecursiveIteratorIterator::callHasChildren() + Called for each element to test whether it has children */ +SPL_METHOD(RecursiveIteratorIterator, callHasChildren) +{ + spl_recursive_it_object *object = (spl_recursive_it_object*)zend_object_store_get_object(getThis() TSRMLS_CC); + zend_class_entry *ce = object->iterators[object->level].ce; + zval *retval, *zobject; + + zobject = object->iterators[object->level].zobject; + if (!zobject) { + RETURN_FALSE; + } else { + zend_call_method_with_0_params(&zobject, ce, NULL, "haschildren", &retval); + RETURN_ZVAL(retval, 0, 1); + } +} /* }}} */ + +/* {{{ proto RecursiveIterator RecursiveIteratorIterator::callGetChildren() + Return children of current element */ +SPL_METHOD(RecursiveIteratorIterator, callGetChildren) +{ + spl_recursive_it_object *object = (spl_recursive_it_object*)zend_object_store_get_object(getThis() TSRMLS_CC); + zend_class_entry *ce = object->iterators[object->level].ce; + zval *retval, *zobject; + + zobject = object->iterators[object->level].zobject; + if (!zobject) { + return; + } else { + zend_call_method_with_0_params(&zobject, ce, NULL, "getchildren", &retval); + RETURN_ZVAL(retval, 0, 1); + } +} /* }}} */ + /* {{{ proto RecursiveIterator RecursiveIteratorIterator::beginChildren() Called when recursing one level down */ SPL_METHOD(RecursiveIteratorIterator, beginChildren) @@ -538,10 +596,12 @@ static zend_function_entry spl_funcs_RecursiveIteratorIterator[] = { SPL_ME(RecursiveIteratorIterator, current, NULL, ZEND_ACC_PUBLIC) SPL_ME(RecursiveIteratorIterator, next, NULL, ZEND_ACC_PUBLIC) SPL_ME(RecursiveIteratorIterator, getDepth, NULL, ZEND_ACC_PUBLIC) - SPL_ME(RecursiveIteratorIterator, getSubIterator,arginfo_recursive_it_getSubIterator, ZEND_ACC_PUBLIC) - SPL_ME(RecursiveIteratorIterator, getInnerIterator,NULL, ZEND_ACC_PUBLIC) - SPL_ME(RecursiveIteratorIterator, beginChildren, NULL, ZEND_ACC_PUBLIC) - SPL_ME(RecursiveIteratorIterator, endChildren, NULL, ZEND_ACC_PUBLIC) + SPL_ME(RecursiveIteratorIterator, getSubIterator, arginfo_recursive_it_getSubIterator, ZEND_ACC_PUBLIC) + SPL_ME(RecursiveIteratorIterator, getInnerIterator, NULL, ZEND_ACC_PUBLIC) + SPL_ME(RecursiveIteratorIterator, callHasChildren, NULL, ZEND_ACC_PUBLIC) + SPL_ME(RecursiveIteratorIterator, callGetChildren, NULL, ZEND_ACC_PUBLIC) + SPL_ME(RecursiveIteratorIterator, beginChildren, NULL, ZEND_ACC_PUBLIC) + SPL_ME(RecursiveIteratorIterator, endChildren, NULL, ZEND_ACC_PUBLIC) {NULL, NULL, NULL} }; diff --git a/ext/spl/tests/iterator_021.phpt b/ext/spl/tests/iterator_021.phpt new file mode 100755 index 0000000000..cb17907229 --- /dev/null +++ b/ext/spl/tests/iterator_021.phpt @@ -0,0 +1,185 @@ +--TEST-- +SPL: RecursiveIteratorIterator and hasChildren +--FILE-- +current()); + } + + function getChildren() + { + echo __METHOD__ . "\n"; + return new RecursiveArrayIterator($this->current()); + } + + function valid() + { + if (!parent::valid()) + { + echo __METHOD__ . " = false\n"; + return false; + } + else + { + return true; + } + } +} + +class RecursiveArrayIteratorIterator extends RecursiveIteratorIterator +{ + private $max_depth; + private $over = 0; + private $skip = false; + + function __construct($it, $max_depth) + { + $this->max_depth = $max_depth; + parent::__construct($it); + } + + function rewind() + { + echo __METHOD__ . "\n"; + $this->skip = false; + parent::rewind(); + } + + function valid() + { + echo __METHOD__ . "\n"; + if ($this->skip) + { + $this->skip = false; + $this->next(); + } + return parent::valid(); + } + + function current() + { + echo __METHOD__ . "\n"; + return parent::current(); + } + + function key() + { + echo __METHOD__ . "\n"; + return parent::key(); + } + + function next() + { + echo __METHOD__ . "\n"; + parent::next(); + } + + function callHasChildren() + { + $this->skip = false; + $has = parent::callHasChildren(); + $res = $this->getDepth() < $this->max_depth && $has; + echo __METHOD__ . "(".$this->getDepth().") = ".($res?"yes":"no")."/".($has?"yes":"no")."\n"; + if ($has && !$res) + { + $this->over++; + if ($this->over == 2) { + $this->skip = true; + } + } + return $res; + } + + function beginChildren() + { + echo __METHOD__ . "(".$this->getDepth().")\n"; + } + + function endChildren() + { + echo __METHOD__ . "(".$this->getDepth().")\n"; + } +} + +foreach(new RecursiveArrayIteratorIterator(new RecursiveArrayIterator(array("a", array("ba", array("bba", "bbb"), array(array("bcaa"), array("bcba"))), array("ca"), "d")), 2) as $k=>$v) +{ + if (is_array($v)) $v = join('',$v); + echo "$k=>$v\n"; +} +?> +===DONE=== + +--EXPECTF-- +RecursiveArrayIteratorIterator::rewind +RecursiveArrayIteratorIterator::callHasChildren(0) = no/no +RecursiveArrayIteratorIterator::valid +RecursiveArrayIteratorIterator::current +RecursiveArrayIteratorIterator::key +0=>a +RecursiveArrayIteratorIterator::next +RecursiveArrayIteratorIterator::callHasChildren(0) = yes/yes +RecursiveArrayIterator::getChildren +RecursiveArrayIteratorIterator::beginChildren(1) +RecursiveArrayIteratorIterator::callHasChildren(1) = no/no +RecursiveArrayIteratorIterator::valid +RecursiveArrayIteratorIterator::current +RecursiveArrayIteratorIterator::key +0=>ba +RecursiveArrayIteratorIterator::next +RecursiveArrayIteratorIterator::callHasChildren(1) = yes/yes +RecursiveArrayIterator::getChildren +RecursiveArrayIteratorIterator::beginChildren(2) +RecursiveArrayIteratorIterator::callHasChildren(2) = no/no +RecursiveArrayIteratorIterator::valid +RecursiveArrayIteratorIterator::current +RecursiveArrayIteratorIterator::key +0=>bba +RecursiveArrayIteratorIterator::next +RecursiveArrayIteratorIterator::callHasChildren(2) = no/no +RecursiveArrayIteratorIterator::valid +RecursiveArrayIteratorIterator::current +RecursiveArrayIteratorIterator::key +1=>bbb +RecursiveArrayIteratorIterator::next +RecursiveArrayIterator::valid = false +RecursiveArrayIteratorIterator::endChildren(1) +RecursiveArrayIteratorIterator::callHasChildren(1) = yes/yes +RecursiveArrayIterator::getChildren +RecursiveArrayIteratorIterator::beginChildren(2) +RecursiveArrayIteratorIterator::callHasChildren(2) = no/yes +RecursiveArrayIteratorIterator::valid +RecursiveArrayIteratorIterator::current +RecursiveArrayIteratorIterator::key +0=>bcaa +RecursiveArrayIteratorIterator::next +RecursiveArrayIteratorIterator::callHasChildren(2) = no/yes +RecursiveArrayIteratorIterator::valid +RecursiveArrayIteratorIterator::next +RecursiveArrayIterator::valid = false +RecursiveArrayIteratorIterator::endChildren(1) +RecursiveArrayIterator::valid = false +RecursiveArrayIteratorIterator::endChildren(0) +RecursiveArrayIteratorIterator::callHasChildren(0) = yes/yes +RecursiveArrayIterator::getChildren +RecursiveArrayIteratorIterator::beginChildren(1) +RecursiveArrayIteratorIterator::callHasChildren(1) = no/no +RecursiveArrayIteratorIterator::current +RecursiveArrayIteratorIterator::key +0=>ca +RecursiveArrayIteratorIterator::next +RecursiveArrayIterator::valid = false +RecursiveArrayIteratorIterator::endChildren(0) +RecursiveArrayIteratorIterator::callHasChildren(0) = no/no +RecursiveArrayIteratorIterator::valid +RecursiveArrayIteratorIterator::current +RecursiveArrayIteratorIterator::key +3=>d +RecursiveArrayIteratorIterator::next +RecursiveArrayIterator::valid = false +RecursiveArrayIteratorIterator::valid +RecursiveArrayIterator::valid = false +===DONE=== diff --git a/ext/spl/tests/iterator_022.phpt b/ext/spl/tests/iterator_022.phpt new file mode 100755 index 0000000000..988eb62d3a --- /dev/null +++ b/ext/spl/tests/iterator_022.phpt @@ -0,0 +1,191 @@ +--TEST-- +SPL: RecursiveIteratorIterator and callHasChildren/callGetChildren +--FILE-- +current()); + } + + function getChildren() + { + echo __METHOD__ . "\n"; + return $this->current(); + } + + function valid() + { + if (!parent::valid()) + { + echo __METHOD__ . " = false\n"; + return false; + } + else + { + return true; + } + } +} + +class RecursiveArrayIteratorIterator extends RecursiveIteratorIterator +{ + private $max_depth; + private $over = 0; + private $skip = false; + + function __construct($it, $max_depth) + { + $this->max_depth = $max_depth; + parent::__construct($it); + } + + function rewind() + { + echo __METHOD__ . "\n"; + $this->skip = false; + parent::rewind(); + } + + function valid() + { + echo __METHOD__ . "\n"; + if ($this->skip) + { + $this->skip = false; + $this->next(); + } + return parent::valid(); + } + + function current() + { + echo __METHOD__ . "\n"; + return parent::current(); + } + + function key() + { + echo __METHOD__ . "\n"; + return parent::key(); + } + + function next() + { + echo __METHOD__ . "\n"; + parent::next(); + } + + function callHasChildren() + { + $this->skip = false; + $has = parent::callHasChildren(); + $res = $this->getDepth() < $this->max_depth && $has; + echo __METHOD__ . "(".$this->getDepth().") = ".($res?"yes":"no")."/".($has?"yes":"no")."\n"; + if ($has && !$res) + { + $this->over++; + if ($this->over == 2) { + $this->skip = true; + } + } + return $res; + } + + function callGetChildren() + { + if ($this->over == 2) + { + echo __METHOD__ . "(skip)\n"; + return NULL; + } + echo __METHOD__ . "(ok:{$this->over})\n"; + return new RecursiveArrayIterator($this->current()); + } + + function beginChildren() + { + echo __METHOD__ . "(".$this->getDepth().")\n"; + } + + function endChildren() + { + echo __METHOD__ . "(".$this->getDepth().")\n"; + } +} + +try +{ + foreach(new RecursiveArrayIteratorIterator(new RecursiveArrayIterator(array("a", array("ba", array("bba", "bbb"), array(array("bcaa"), array("bcba"))), array("ca"), "d")), 2) as $k=>$v) + { + if (is_array($v)) $v = join('',$v); + echo "$k=>$v\n"; + } +} +catch(UnexpectedValueException $e) +{ + echo $e->getMessage() . "\n"; +} + +?> +===DONE=== + +--EXPECTF-- +RecursiveArrayIteratorIterator::rewind +RecursiveArrayIteratorIterator::callHasChildren(0) = no/no +RecursiveArrayIteratorIterator::valid +RecursiveArrayIteratorIterator::current +RecursiveArrayIteratorIterator::key +0=>a +RecursiveArrayIteratorIterator::next +RecursiveArrayIteratorIterator::callHasChildren(0) = yes/yes +RecursiveArrayIteratorIterator::callGetChildren(ok:0) +RecursiveArrayIteratorIterator::current +RecursiveArrayIteratorIterator::beginChildren(1) +RecursiveArrayIteratorIterator::callHasChildren(1) = no/no +RecursiveArrayIteratorIterator::valid +RecursiveArrayIteratorIterator::current +RecursiveArrayIteratorIterator::key +0=>ba +RecursiveArrayIteratorIterator::next +RecursiveArrayIteratorIterator::callHasChildren(1) = yes/yes +RecursiveArrayIteratorIterator::callGetChildren(ok:0) +RecursiveArrayIteratorIterator::current +RecursiveArrayIteratorIterator::beginChildren(2) +RecursiveArrayIteratorIterator::callHasChildren(2) = no/no +RecursiveArrayIteratorIterator::valid +RecursiveArrayIteratorIterator::current +RecursiveArrayIteratorIterator::key +0=>bba +RecursiveArrayIteratorIterator::next +RecursiveArrayIteratorIterator::callHasChildren(2) = no/no +RecursiveArrayIteratorIterator::valid +RecursiveArrayIteratorIterator::current +RecursiveArrayIteratorIterator::key +1=>bbb +RecursiveArrayIteratorIterator::next +RecursiveArrayIterator::valid = false +RecursiveArrayIteratorIterator::endChildren(1) +RecursiveArrayIteratorIterator::callHasChildren(1) = yes/yes +RecursiveArrayIteratorIterator::callGetChildren(ok:0) +RecursiveArrayIteratorIterator::current +RecursiveArrayIteratorIterator::beginChildren(2) +RecursiveArrayIteratorIterator::callHasChildren(2) = no/yes +RecursiveArrayIteratorIterator::valid +RecursiveArrayIteratorIterator::current +RecursiveArrayIteratorIterator::key +0=>bcaa +RecursiveArrayIteratorIterator::next +RecursiveArrayIteratorIterator::callHasChildren(2) = no/yes +RecursiveArrayIteratorIterator::valid +RecursiveArrayIteratorIterator::next +RecursiveArrayIterator::valid = false +RecursiveArrayIteratorIterator::endChildren(1) +RecursiveArrayIterator::valid = false +RecursiveArrayIteratorIterator::endChildren(0) +RecursiveArrayIteratorIterator::callHasChildren(0) = yes/yes +RecursiveArrayIteratorIterator::callGetChildren(skip) +Objects returned by RecursiveIterator::getChildren() must implement RecursiveIterator +===DONE=== -- 2.50.1