From 7ae7df5b4601f3f0ce45a27324fb9c6ebcbfc9ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Tue, 19 Jul 2022 16:46:32 +0200 Subject: [PATCH] RFC: Make the `iterator_*()` family accept all `iterable`s (#8819) https://wiki.php.net/rfc/iterator_xyz_accept_array --- NEWS | 4 ++ UPGRADING | 5 +++ ext/spl/php_spl.stub.php | 4 +- ext/spl/php_spl_arginfo.h | 6 +-- ext/spl/spl_iterators.c | 28 ++++++++++---- ext/spl/tests/iterator_count.phpt | 2 +- ext/spl/tests/iterator_count_array.phpt | 14 +++++++ ext/spl/tests/iterator_count_exception.phpt | 20 ++++++++++ ext/spl/tests/iterator_to_array.phpt | 2 +- ext/spl/tests/iterator_to_array_array.phpt | 42 +++++++++++++++++++++ 10 files changed, 113 insertions(+), 14 deletions(-) create mode 100644 ext/spl/tests/iterator_count_array.phpt create mode 100644 ext/spl/tests/iterator_count_exception.phpt create mode 100644 ext/spl/tests/iterator_to_array_array.phpt diff --git a/NEWS b/NEWS index b1364eeddc0..e33db97cc42 100644 --- a/NEWS +++ b/NEWS @@ -48,6 +48,10 @@ PHP NEWS - Random: . Added new random extension. (Go Kudo) +- SPL: + . Widen iterator_to_array() and iterator_count()'s $iterator parameter to + iterable. (timwolla) + - Standard: . Fixed empty array returned by str_split on empty input. (Michael Vorisek) . Added ini_parse_quantity function to convert ini quantities shorthand diff --git a/UPGRADING b/UPGRADING index 91840a43534..bde0f9442e0 100644 --- a/UPGRADING +++ b/UPGRADING @@ -189,6 +189,11 @@ PHP 8.2 UPGRADE NOTES dba_fetch(string|array $key, $skip, $dba): string|false is still accepted, but it is recommended to use the new standard variant. +- SPL + . The $iterator parameter of iterator_to_array() and iterator_count() is + widened to iterable from Iterator, allowing arrays to be passed. + RFC: https://wiki.php.net/rfc/iterator_xyz_accept_array + ======================================== 6. New Functions ======================================== diff --git a/ext/spl/php_spl.stub.php b/ext/spl/php_spl.stub.php index c879f1fa5e5..7c120cea1aa 100644 --- a/ext/spl/php_spl.stub.php +++ b/ext/spl/php_spl.stub.php @@ -48,7 +48,7 @@ function spl_object_id(object $object): int {} function iterator_apply(Traversable $iterator, callable $callback, ?array $args = null): int {} -function iterator_count(Traversable $iterator): int {} +function iterator_count(iterable $iterator): int {} /** @refcount 1 */ -function iterator_to_array(Traversable $iterator, bool $preserve_keys = true): array {} +function iterator_to_array(iterable $iterator, bool $preserve_keys = true): array {} diff --git a/ext/spl/php_spl_arginfo.h b/ext/spl/php_spl_arginfo.h index 2565e935ae4..2a278a375bf 100644 --- a/ext/spl/php_spl_arginfo.h +++ b/ext/spl/php_spl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 7359e9e5aa07c00d0e0be2642f11c1131a17e61e */ + * Stub hash: 2590b06c4e9798c2ceba9aa364f66d05c5690bc1 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_implements, 0, 1, MAY_BE_ARRAY|MAY_BE_FALSE) ZEND_ARG_INFO(0, object_or_class) @@ -53,11 +53,11 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_iterator_apply, 0, 2, IS_LONG, 0 ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_iterator_count, 0, 1, IS_LONG, 0) - ZEND_ARG_OBJ_INFO(0, iterator, Traversable, 0) + ZEND_ARG_OBJ_TYPE_MASK(0, iterator, Traversable, MAY_BE_ARRAY, NULL) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_iterator_to_array, 0, 1, IS_ARRAY, 0) - ZEND_ARG_OBJ_INFO(0, iterator, Traversable, 0) + ZEND_ARG_OBJ_TYPE_MASK(0, iterator, Traversable, MAY_BE_ARRAY, NULL) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, preserve_keys, _IS_BOOL, 0, "true") ZEND_END_ARG_INFO() diff --git a/ext/spl/spl_iterators.c b/ext/spl/spl_iterators.c index f46c869738e..e920bac0136 100644 --- a/ext/spl/spl_iterators.c +++ b/ext/spl/spl_iterators.c @@ -3171,8 +3171,18 @@ PHP_FUNCTION(iterator_to_array) zval *obj; bool use_keys = 1; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "O|b", &obj, zend_ce_traversable, &use_keys) == FAILURE) { - RETURN_THROWS(); + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_ITERABLE(obj) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(use_keys) + ZEND_PARSE_PARAMETERS_END(); + + if (Z_TYPE_P(obj) == IS_ARRAY) { + if (use_keys) { + RETURN_COPY(obj); + } else { + RETURN_ARR(zend_array_to_list(Z_ARRVAL_P(obj))); + } } array_init(return_value); @@ -3195,12 +3205,16 @@ PHP_FUNCTION(iterator_count) zval *obj; zend_long count = 0; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &obj, zend_ce_traversable) == FAILURE) { - RETURN_THROWS(); - } + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ITERABLE(obj) + ZEND_PARSE_PARAMETERS_END(); - if (spl_iterator_apply(obj, spl_iterator_count_apply, (void*)&count) == FAILURE) { - return; + if (Z_TYPE_P(obj) == IS_ARRAY) { + count = zend_hash_num_elements(Z_ARRVAL_P(obj)); + } else { + if (spl_iterator_apply(obj, spl_iterator_count_apply, (void*)&count) == FAILURE) { + RETURN_THROWS(); + } } RETURN_LONG(count); diff --git a/ext/spl/tests/iterator_count.phpt b/ext/spl/tests/iterator_count.phpt index 62f9ee83344..db2265f824f 100644 --- a/ext/spl/tests/iterator_count.phpt +++ b/ext/spl/tests/iterator_count.phpt @@ -13,7 +13,7 @@ iterator_count('1'); ?> --EXPECTF-- -Fatal error: Uncaught TypeError: iterator_count(): Argument #1 ($iterator) must be of type Traversable, string given in %s:%d +Fatal error: Uncaught TypeError: iterator_count(): Argument #1 ($iterator) must be of type Traversable|array, string given in %s:%d Stack trace: #0 %s(%d): iterator_count('1') #1 {main} diff --git a/ext/spl/tests/iterator_count_array.phpt b/ext/spl/tests/iterator_count_array.phpt new file mode 100644 index 00000000000..2fdd1a4d2bb --- /dev/null +++ b/ext/spl/tests/iterator_count_array.phpt @@ -0,0 +1,14 @@ +--TEST-- +SPL: iterator_count() supports arrays. +--FILE-- + 1, 'b' => 2, 5 => 3])); + +?> +--EXPECT-- +int(0) +int(1) +int(3) diff --git a/ext/spl/tests/iterator_count_exception.phpt b/ext/spl/tests/iterator_count_exception.phpt new file mode 100644 index 00000000000..f15b63ec981 --- /dev/null +++ b/ext/spl/tests/iterator_count_exception.phpt @@ -0,0 +1,20 @@ +--TEST-- +SPL: iterator_count() throws if the Iterator throws +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Exception: Iterator failed in %siterator_count_exception.php:5 +Stack trace: +#0 [internal function]: generator() +#1 %siterator_count_exception.php(8): iterator_count(Object(Generator)) +#2 {main} + thrown in %siterator_count_exception.php on line 5 diff --git a/ext/spl/tests/iterator_to_array.phpt b/ext/spl/tests/iterator_to_array.phpt index a754c9da6fb..ae937e2d774 100644 --- a/ext/spl/tests/iterator_to_array.phpt +++ b/ext/spl/tests/iterator_to_array.phpt @@ -13,7 +13,7 @@ iterator_to_array('test','test'); ?> --EXPECTF-- -Fatal error: Uncaught TypeError: iterator_to_array(): Argument #1 ($iterator) must be of type Traversable, string given in %s:%d +Fatal error: Uncaught TypeError: iterator_to_array(): Argument #1 ($iterator) must be of type Traversable|array, string given in %s:%d Stack trace: #0 %s(%d): iterator_to_array('test', 'test') #1 {main} diff --git a/ext/spl/tests/iterator_to_array_array.phpt b/ext/spl/tests/iterator_to_array_array.phpt new file mode 100644 index 00000000000..527d281f36e --- /dev/null +++ b/ext/spl/tests/iterator_to_array_array.phpt @@ -0,0 +1,42 @@ +--TEST-- +SPL: iterator_to_array() supports arrays. +--FILE-- + 1, 'b' => 2, 5 => 3])); +var_dump(iterator_to_array([], false)); +var_dump(iterator_to_array([1], false)); +var_dump(iterator_to_array(['a' => 1, 'b' => 2, 5 => 3], false)); + +?> +--EXPECT-- +array(0) { +} +array(1) { + [0]=> + int(1) +} +array(3) { + ["a"]=> + int(1) + ["b"]=> + int(2) + [5]=> + int(3) +} +array(0) { +} +array(1) { + [0]=> + int(1) +} +array(3) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) +}