Skip to content

Commit e98ca8c

Browse files
bugreportuserTyson Andre
authored andcommitted
Create array_every() and array_any()
Create iterator_every() and iterator_any() This has merge conflicts which were resolved in the subsequent commit(s). This commit was used as a basis for implementing an alternate approach to checking if all()/any() elements in an iterable satisfy a predicate. Cherry-picked from GH-3597 Co-Authored-By: bugreportuser Co-Authored-By: Tyson Andre <[email protected]>
1 parent 1902f73 commit e98ca8c

11 files changed

+513
-0
lines changed

ext/spl/spl_iterators.c

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3166,6 +3166,89 @@ PHP_FUNCTION(iterator_apply)
31663166
}
31673167
/* }}} */
31683168

3169+
typedef struct {
3170+
zval *obj;
3171+
int stop_value;
3172+
int result;
3173+
int found;
3174+
zend_fcall_info fci;
3175+
zend_fcall_info_cache fcc;
3176+
} spl_iterator_until_info;
3177+
3178+
static int spl_iterator_func_until(zend_object_iterator *iter, void *puser) /* {{{ */
3179+
{
3180+
zval args[2];
3181+
zval key;
3182+
zend_fcall_info fci;
3183+
zval retval;
3184+
spl_iterator_until_info *until_info = (spl_iterator_until_info*) puser;
3185+
int result;
3186+
3187+
ZVAL_COPY(&args[0], iter->funcs->get_current_data(iter));
3188+
iter->funcs->get_current_key(iter, &key);
3189+
ZVAL_COPY(&args[1], &key);
3190+
3191+
fci = until_info->fci;
3192+
fci.param_count = 2;
3193+
fci.params = args;
3194+
fci.retval = &retval;
3195+
fci.no_separation = 0;
3196+
3197+
result = zend_call_function(&fci, &until_info->fcc);
3198+
zval_ptr_dtor(&args[0]);
3199+
zval_ptr_dtor(&args[1]);
3200+
if (result == FAILURE) {
3201+
until_info->result = FAILURE;
3202+
return ZEND_HASH_APPLY_STOP;
3203+
}
3204+
3205+
if (zend_is_true(&retval) == until_info->stop_value) {
3206+
until_info->found = 1;
3207+
return ZEND_HASH_APPLY_STOP;
3208+
}
3209+
return ZEND_HASH_APPLY_KEEP;
3210+
}
3211+
/* }}} */
3212+
3213+
static inline void php_iterator_until(INTERNAL_FUNCTION_PARAMETERS, int stop_value) /* {{{ */
3214+
{
3215+
spl_iterator_until_info until_info;
3216+
3217+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "Of", &until_info.obj, zend_ce_traversable, &until_info.fci, &until_info.fcc) == FAILURE) {
3218+
return;
3219+
}
3220+
3221+
until_info.stop_value = stop_value;
3222+
until_info.result = SUCCESS;
3223+
until_info.found = 0;
3224+
3225+
if (spl_iterator_apply(until_info.obj, spl_iterator_func_until, (void*)&until_info) == SUCCESS && until_info.result == SUCCESS) {
3226+
RETURN_BOOL(!(until_info.found ^ stop_value));
3227+
}
3228+
}
3229+
/* }}} */
3230+
3231+
/* {{{ proto bool iterator_every(Traversable iterator, mixed predicate)
3232+
Determines whether the predicate holds for all elements in the iterator */
3233+
PHP_FUNCTION(iterator_every)
3234+
{
3235+
php_iterator_until(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
3236+
}
3237+
/* }}} */
3238+
3239+
/* {{{ proto bool iterator_any(Traversable iterator, mixed predicate)
3240+
Determines whether the predicate holds for at least one element in the iterator */
3241+
PHP_FUNCTION(iterator_any)
3242+
{
3243+
php_iterator_until(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
3244+
}
3245+
/* }}} */
3246+
3247+
static const zend_function_entry spl_funcs_OuterIterator[] = {
3248+
SPL_ABSTRACT_ME(OuterIterator, getInnerIterator, arginfo_recursive_it_void)
3249+
PHP_FE_END
3250+
};
3251+
31693252
/* {{{ PHP_MINIT_FUNCTION(spl_iterators) */
31703253
PHP_MINIT_FUNCTION(spl_iterators)
31713254
{

ext/spl/spl_iterators.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ PHP_MINIT_FUNCTION(spl_iterators);
5555
PHP_FUNCTION(iterator_to_array);
5656
PHP_FUNCTION(iterator_count);
5757
PHP_FUNCTION(iterator_apply);
58+
PHP_FUNCTION(iterator_every);
59+
PHP_FUNCTION(iterator_any);
5860

5961
typedef enum {
6062
DIT_Default = 0,

ext/spl/tests/iterator_any_001.phpt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
SPL: iterator_any() bool argument exception test
3+
--FILE--
4+
<?php
5+
iterator_any(true);
6+
?>
7+
--EXPECTF--
8+
Fatal error: Uncaught TypeError: Argument 1 passed to iterator_any() must implement interface Traversable, bool given in %s:%d
9+
Stack trace:
10+
#0 %s(%d): iterator_any(true)
11+
#1 {main}
12+
thrown in %s on line %d

ext/spl/tests/iterator_any_002.phpt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
SPL: iterator_any() array argument exception test
3+
--FILE--
4+
<?php
5+
iterator_any([]);
6+
?>
7+
--EXPECTF--
8+
Fatal error: Uncaught TypeError: Argument 1 passed to iterator_any() must implement interface Traversable, array given in %s:%d
9+
Stack trace:
10+
#0 %s(%d): iterator_any(Array)
11+
#1 {main}
12+
thrown in %s on line %d

ext/spl/tests/iterator_any_003.phpt

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
--TEST--
2+
Test iterator_any() function
3+
--FILE--
4+
<?php
5+
/*
6+
Prototype: bool iterator_any(array $array, mixed $callback);
7+
Description: Iterate array and stop based on return value of callback
8+
*/
9+
10+
function is_int_ex($nr)
11+
{
12+
return is_int($nr);
13+
}
14+
15+
echo "\n*** Testing not enough or wrong arguments ***\n";
16+
17+
var_dump(iterator_any());
18+
var_dump(iterator_any(new ArrayIterator()));
19+
var_dump(iterator_any(new ArrayIterator(), true));
20+
21+
echo "\n*** Testing basic functionality ***\n";
22+
23+
var_dump(iterator_any(new ArrayIterator(['hello', 'world']), 'is_int_ex'));
24+
var_dump(iterator_any(new ArrayIterator(['hello', 1, 2, 3]), 'is_int_ex'));
25+
$iterations = 0;
26+
var_dump(iterator_any(new ArrayIterator(['hello', 1, 2, 3]), function($item) use (&$iterations) {
27+
++$iterations;
28+
return is_int($item);
29+
}));
30+
var_dump($iterations);
31+
32+
echo "\n*** Testing second argument to predicate ***\n";
33+
34+
var_dump(iterator_any(new ArrayIterator([1, 2, 3]), function($item, $key) {
35+
var_dump($key);
36+
return false;
37+
}));
38+
39+
echo "\n*** Testing edge cases ***\n";
40+
41+
var_dump(iterator_any(new ArrayIterator(), 'is_int_ex'));
42+
43+
echo "\nDone";
44+
?>
45+
--EXPECTF--
46+
*** Testing not enough or wrong arguments ***
47+
48+
Warning: iterator_any() expects exactly 2 parameters, 0 given in %s on line %d
49+
NULL
50+
51+
Warning: iterator_any() expects exactly 2 parameters, 1 given in %s on line %d
52+
NULL
53+
54+
Warning: iterator_any() expects parameter 2 to be a valid callback, no array or string given in %s on line %d
55+
NULL
56+
57+
*** Testing basic functionality ***
58+
bool(false)
59+
bool(true)
60+
bool(true)
61+
int(2)
62+
63+
*** Testing second argument to predicate ***
64+
int(0)
65+
int(1)
66+
int(2)
67+
bool(false)
68+
69+
*** Testing edge cases ***
70+
bool(false)
71+
72+
Done

ext/spl/tests/iterator_every_001.phpt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
SPL: iterator_every() bool argument exception test
3+
--FILE--
4+
<?php
5+
iterator_every(true);
6+
?>
7+
--EXPECTF--
8+
Fatal error: Uncaught TypeError: Argument 1 passed to iterator_every() must implement interface Traversable, bool given in %s:%d
9+
Stack trace:
10+
#0 %s(%d): iterator_every(true)
11+
#1 {main}
12+
thrown in %s on line %d

ext/spl/tests/iterator_every_002.phpt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
SPL: iterator_every() array argument exception test
3+
--FILE--
4+
<?php
5+
iterator_every([]);
6+
?>
7+
--EXPECTF--
8+
Fatal error: Uncaught TypeError: Argument 1 passed to iterator_every() must implement interface Traversable, array given in %s:%d
9+
Stack trace:
10+
#0 %s(%d): iterator_every(Array)
11+
#1 {main}
12+
thrown in %s on line %d

ext/spl/tests/iterator_every_003.phpt

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
--TEST--
2+
Test iterator_every() function
3+
--FILE--
4+
<?php
5+
/*
6+
Prototype: bool iterator_every(array $array, mixed $callback);
7+
Description: Iterate array and stop based on return value of callback
8+
*/
9+
10+
function is_int_ex($item)
11+
{
12+
return is_int($item);
13+
}
14+
15+
echo "\n*** Testing not enough or wrong arguments ***\n";
16+
17+
var_dump(iterator_every());
18+
var_dump(iterator_every(new ArrayIterator()));
19+
var_dump(iterator_every(new ArrayIterator(), true));
20+
21+
echo "\n*** Testing basic functionality ***\n";
22+
23+
var_dump(iterator_every(new ArrayIterator([1, 2, 3]), 'is_int_ex'));
24+
var_dump(iterator_every(new ArrayIterator(['hello', 1, 2, 3]), 'is_int_ex'));
25+
$iterations = 0;
26+
var_dump(iterator_every(new ArrayIterator(['hello', 1, 2, 3]), function($item) use (&$iterations) {
27+
++$iterations;
28+
return is_int($item);
29+
}));
30+
var_dump($iterations);
31+
32+
echo "\n*** Testing second argument to predicate ***\n";
33+
34+
var_dump(iterator_every(new ArrayIterator([1, 2, 3]), function($item, $key) {
35+
var_dump($key);
36+
return true;
37+
}));
38+
39+
echo "\n*** Testing edge cases ***\n";
40+
41+
var_dump(iterator_every(new ArrayIterator(), 'is_int_ex'));
42+
43+
echo "\nDone";
44+
?>
45+
--EXPECTF--
46+
*** Testing not enough or wrong arguments ***
47+
48+
Warning: iterator_every() expects exactly 2 parameters, 0 given in %s on line %d
49+
NULL
50+
51+
Warning: iterator_every() expects exactly 2 parameters, 1 given in %s on line %d
52+
NULL
53+
54+
Warning: iterator_every() expects parameter 2 to be a valid callback, no array or string given in %s on line %d
55+
NULL
56+
57+
*** Testing basic functionality ***
58+
bool(true)
59+
bool(false)
60+
bool(false)
61+
int(1)
62+
63+
*** Testing second argument to predicate ***
64+
int(0)
65+
int(1)
66+
int(2)
67+
bool(true)
68+
69+
*** Testing edge cases ***
70+
bool(true)
71+
72+
Done

ext/standard/array.c

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6389,3 +6389,79 @@ PHP_FUNCTION(array_combine)
63896389
} ZEND_HASH_FOREACH_END();
63906390
}
63916391
/* }}} */
6392+
6393+
static inline void php_array_until(INTERNAL_FUNCTION_PARAMETERS, int stop_value) /* {{{ */
6394+
{
6395+
zval *input;
6396+
zval args[2];
6397+
zend_ulong num_idx;
6398+
zend_string *str_idx;
6399+
zval *entry;
6400+
zval retval;
6401+
zend_fcall_info fci;
6402+
zend_fcall_info_cache fci_cache = empty_fcall_info_cache;
6403+
HashTable *htbl;
6404+
int result = SUCCESS;
6405+
int found = 0;
6406+
6407+
ZEND_PARSE_PARAMETERS_START(2, 2)
6408+
Z_PARAM_ARRAY(input)
6409+
Z_PARAM_FUNC(fci, fci_cache)
6410+
ZEND_PARSE_PARAMETERS_END();
6411+
6412+
/* (zval **)input points to an element of argument stack
6413+
* the base pointer of which is subject to change.
6414+
* thus we need to keep the pointer to the hashtable for safety */
6415+
htbl = Z_ARRVAL_P(input);
6416+
6417+
if (zend_hash_num_elements(htbl) == 0) {
6418+
RETURN_BOOL(!stop_value);
6419+
}
6420+
6421+
ZEND_HASH_FOREACH_KEY_VAL(htbl, num_idx, str_idx, entry) {
6422+
ZVAL_COPY(&args[0], entry);
6423+
if (str_idx) {
6424+
ZVAL_STR_COPY(&args[1], str_idx);
6425+
}
6426+
else {
6427+
ZVAL_LONG(&args[1], num_idx);
6428+
}
6429+
6430+
fci.param_count = 2;
6431+
fci.params = args;
6432+
fci.retval = &retval;
6433+
fci.no_separation = 0;
6434+
6435+
result = zend_call_function(&fci, &fci_cache);
6436+
zval_ptr_dtor(&args[0]);
6437+
zval_ptr_dtor(&args[1]);
6438+
if (result == FAILURE) {
6439+
return;
6440+
}
6441+
6442+
if (zend_is_true(&retval) == stop_value) {
6443+
RETURN_BOOL(stop_value);
6444+
}
6445+
} ZEND_HASH_FOREACH_END();
6446+
6447+
if (result == SUCCESS) {
6448+
RETURN_BOOL(!stop_value);
6449+
}
6450+
}
6451+
/* }}} */
6452+
6453+
/* {{{ proto bool array_every(array input, mixed predicate)
6454+
Determines whether the predicate holds for all elements in the array */
6455+
PHP_FUNCTION(array_every)
6456+
{
6457+
php_array_until(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
6458+
}
6459+
/* }}} */
6460+
6461+
/* {{{ proto bool array_any(array input, mixed predicate)
6462+
Determines whether the predicate holds for at least one element in the array */
6463+
PHP_FUNCTION(array_any)
6464+
{
6465+
php_array_until(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
6466+
}
6467+
/* }}} */

0 commit comments

Comments
 (0)