Skip to content

Implementing array_every() and array_any() #3597

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ext/opcache/Optimizer/zend_func_info.c
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,8 @@ static const func_info_t func_infos[] = {
FN("array_map", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
F1("array_chunk", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
F1("array_combine", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY),
F1("array_every", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE),
F1("array_any", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE),
F0("array_key_exists", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_TRUE),
F1("pos", UNKNOWN_INFO),
F0("sizeof", MAY_BE_NULL | MAY_BE_LONG),
Expand Down
7 changes: 7 additions & 0 deletions ext/spl/php_spl.c
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,11 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_iterator_apply, 0, 0, 2)
ZEND_ARG_ARRAY_INFO(0, args, 1)
ZEND_END_ARG_INFO();

ZEND_BEGIN_ARG_INFO_EX(arginfo_iterator_until, 0, 0, 2)
ZEND_ARG_OBJ_INFO(0, iterator, Traversable, 0)
ZEND_ARG_INFO(0, function)
ZEND_END_ARG_INFO();

ZEND_BEGIN_ARG_INFO_EX(arginfo_class_parents, 0, 0, 1)
ZEND_ARG_INFO(0, instance)
ZEND_ARG_INFO(0, autoload)
Expand Down Expand Up @@ -984,6 +989,8 @@ static const zend_function_entry spl_functions[] = {
PHP_FE(iterator_to_array, arginfo_iterator_to_array)
PHP_FE(iterator_count, arginfo_iterator)
PHP_FE(iterator_apply, arginfo_iterator_apply)
PHP_FE(iterator_every, arginfo_iterator_until)
PHP_FE(iterator_any, arginfo_iterator_until)
#endif /* SPL_ITERATORS_H */
PHP_FE_END
};
Expand Down
78 changes: 78 additions & 0 deletions ext/spl/spl_iterators.c
Original file line number Diff line number Diff line change
Expand Up @@ -3672,6 +3672,84 @@ PHP_FUNCTION(iterator_apply)
}
/* }}} */

typedef struct {
zval *obj;
int stop_value;
int result;
int found;
zend_fcall_info fci;
zend_fcall_info_cache fcc;
} spl_iterator_until_info;

static int spl_iterator_func_until(zend_object_iterator *iter, void *puser) /* {{{ */
{
zval args[2];
zval key;
zend_fcall_info fci;
zval retval;
spl_iterator_until_info *until_info = (spl_iterator_until_info*) puser;
int result;

ZVAL_COPY(&args[0], iter->funcs->get_current_data(iter));
iter->funcs->get_current_key(iter, &key);
ZVAL_COPY(&args[1], &key);

fci = until_info->fci;
fci.param_count = 2;
fci.params = args;
fci.retval = &retval;
fci.no_separation = 0;

result = zend_call_function(&fci, &until_info->fcc);
zval_ptr_dtor(&args[0]);
zval_ptr_dtor(&args[1]);
if (result == FAILURE) {
until_info->result = FAILURE;
return ZEND_HASH_APPLY_STOP;
}

if (zend_is_true(&retval) == until_info->stop_value) {
until_info->found = 1;
return ZEND_HASH_APPLY_STOP;
}
return ZEND_HASH_APPLY_KEEP;
}
/* }}} */

static inline void php_iterator_until(INTERNAL_FUNCTION_PARAMETERS, int stop_value) /* {{{ */
{
spl_iterator_until_info until_info;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "Of", &until_info.obj, zend_ce_traversable, &until_info.fci, &until_info.fcc) == FAILURE) {
return;
}

until_info.stop_value = stop_value;
until_info.result = SUCCESS;
until_info.found = 0;

if (spl_iterator_apply(until_info.obj, spl_iterator_func_until, (void*)&until_info) == SUCCESS && until_info.result == SUCCESS) {
RETURN_BOOL(!(until_info.found ^ stop_value));
}
}
/* }}} */

/* {{{ proto bool iterator_every(Traversable iterator, mixed predicate)
Determines whether the predicate holds for all elements in the iterator */
PHP_FUNCTION(iterator_every)
{
php_iterator_until(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}
/* }}} */

/* {{{ proto bool iterator_any(Traversable iterator, mixed predicate)
Determines whether the predicate holds for at least one element in the iterator */
PHP_FUNCTION(iterator_any)
{
php_iterator_until(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
}
/* }}} */

static const zend_function_entry spl_funcs_OuterIterator[] = {
SPL_ABSTRACT_ME(OuterIterator, getInnerIterator, arginfo_recursive_it_void)
PHP_FE_END
Expand Down
2 changes: 2 additions & 0 deletions ext/spl/spl_iterators.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ PHP_MINIT_FUNCTION(spl_iterators);
PHP_FUNCTION(iterator_to_array);
PHP_FUNCTION(iterator_count);
PHP_FUNCTION(iterator_apply);
PHP_FUNCTION(iterator_every);
PHP_FUNCTION(iterator_any);

typedef enum {
DIT_Default = 0,
Expand Down
12 changes: 12 additions & 0 deletions ext/spl/tests/iterator_any_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
SPL: iterator_any() bool argument exception test
--FILE--
<?php
iterator_any(true);
?>
--EXPECTF--
Fatal error: Uncaught TypeError: Argument 1 passed to iterator_any() must implement interface Traversable, bool given in %s:%d
Stack trace:
#0 %s(%d): iterator_any(true)
#1 {main}
thrown in %s on line %d
12 changes: 12 additions & 0 deletions ext/spl/tests/iterator_any_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
SPL: iterator_any() array argument exception test
--FILE--
<?php
iterator_any([]);
?>
--EXPECTF--
Fatal error: Uncaught TypeError: Argument 1 passed to iterator_any() must implement interface Traversable, array given in %s:%d
Stack trace:
#0 %s(%d): iterator_any(Array)
#1 {main}
thrown in %s on line %d
72 changes: 72 additions & 0 deletions ext/spl/tests/iterator_any_003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
--TEST--
Test iterator_any() function
--FILE--
<?php
/*
Prototype: bool iterator_any(array $array, mixed $callback);
Description: Iterate array and stop based on return value of callback
*/

function is_int_ex($nr)
{
return is_int($nr);
}

echo "\n*** Testing not enough or wrong arguments ***\n";

var_dump(iterator_any());
var_dump(iterator_any(new ArrayIterator()));
var_dump(iterator_any(new ArrayIterator(), true));

echo "\n*** Testing basic functionality ***\n";

var_dump(iterator_any(new ArrayIterator(['hello', 'world']), 'is_int_ex'));
var_dump(iterator_any(new ArrayIterator(['hello', 1, 2, 3]), 'is_int_ex'));
$iterations = 0;
var_dump(iterator_any(new ArrayIterator(['hello', 1, 2, 3]), function($item) use (&$iterations) {
++$iterations;
return is_int($item);
}));
var_dump($iterations);

echo "\n*** Testing second argument to predicate ***\n";

var_dump(iterator_any(new ArrayIterator([1, 2, 3]), function($item, $key) {
var_dump($key);
return false;
}));

echo "\n*** Testing edge cases ***\n";

var_dump(iterator_any(new ArrayIterator(), 'is_int_ex'));

echo "\nDone";
?>
--EXPECTF--
*** Testing not enough or wrong arguments ***

Warning: iterator_any() expects exactly 2 parameters, 0 given in %s on line %d
NULL

Warning: iterator_any() expects exactly 2 parameters, 1 given in %s on line %d
NULL

Warning: iterator_any() expects parameter 2 to be a valid callback, no array or string given in %s on line %d
NULL

*** Testing basic functionality ***
bool(false)
bool(true)
bool(true)
int(2)

*** Testing second argument to predicate ***
int(0)
int(1)
int(2)
bool(false)

*** Testing edge cases ***
bool(false)

Done
12 changes: 12 additions & 0 deletions ext/spl/tests/iterator_every_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
SPL: iterator_every() bool argument exception test
--FILE--
<?php
iterator_every(true);
?>
--EXPECTF--
Fatal error: Uncaught TypeError: Argument 1 passed to iterator_every() must implement interface Traversable, bool given in %s:%d
Stack trace:
#0 %s(%d): iterator_every(true)
#1 {main}
thrown in %s on line %d
12 changes: 12 additions & 0 deletions ext/spl/tests/iterator_every_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
SPL: iterator_every() array argument exception test
--FILE--
<?php
iterator_every([]);
?>
--EXPECTF--
Fatal error: Uncaught TypeError: Argument 1 passed to iterator_every() must implement interface Traversable, array given in %s:%d
Stack trace:
#0 %s(%d): iterator_every(Array)
#1 {main}
thrown in %s on line %d
72 changes: 72 additions & 0 deletions ext/spl/tests/iterator_every_003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
--TEST--
Test iterator_every() function
--FILE--
<?php
/*
Prototype: bool iterator_every(array $array, mixed $callback);
Description: Iterate array and stop based on return value of callback
*/

function is_int_ex($item)
{
return is_int($item);
}

echo "\n*** Testing not enough or wrong arguments ***\n";

var_dump(iterator_every());
var_dump(iterator_every(new ArrayIterator()));
var_dump(iterator_every(new ArrayIterator(), true));

echo "\n*** Testing basic functionality ***\n";

var_dump(iterator_every(new ArrayIterator([1, 2, 3]), 'is_int_ex'));
var_dump(iterator_every(new ArrayIterator(['hello', 1, 2, 3]), 'is_int_ex'));
$iterations = 0;
var_dump(iterator_every(new ArrayIterator(['hello', 1, 2, 3]), function($item) use (&$iterations) {
++$iterations;
return is_int($item);
}));
var_dump($iterations);

echo "\n*** Testing second argument to predicate ***\n";

var_dump(iterator_every(new ArrayIterator([1, 2, 3]), function($item, $key) {
var_dump($key);
return true;
}));

echo "\n*** Testing edge cases ***\n";

var_dump(iterator_every(new ArrayIterator(), 'is_int_ex'));

echo "\nDone";
?>
--EXPECTF--
*** Testing not enough or wrong arguments ***

Warning: iterator_every() expects exactly 2 parameters, 0 given in %s on line %d
NULL

Warning: iterator_every() expects exactly 2 parameters, 1 given in %s on line %d
NULL

Warning: iterator_every() expects parameter 2 to be a valid callback, no array or string given in %s on line %d
NULL

*** Testing basic functionality ***
bool(true)
bool(false)
bool(false)
int(1)

*** Testing second argument to predicate ***
int(0)
int(1)
int(2)
bool(true)

*** Testing edge cases ***
bool(true)

Done
Loading