Skip to content

Commit 1b4e5c0

Browse files
bugreportuserTyson Andre
authored andcommitted
Add any(...) and all(iterable $input, ?callable $cb=null)
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 with modifications Combine helpers into any() and all(), modernize for php 8 Properly free the result of the callback if it was a refcounted type such as a string/array. Fix the default value of the callback. Co-Authored-By: bugreportuser Co-Authored-By: Tyson Andre <[email protected]>
1 parent fd36b08 commit 1b4e5c0

File tree

7 files changed

+467
-1
lines changed

7 files changed

+467
-1
lines changed

ext/standard/array.c

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6264,3 +6264,154 @@ PHP_FUNCTION(array_combine)
62646264
} ZEND_HASH_FOREACH_END();
62656265
}
62666266
/* }}} */
6267+
6268+
static inline void php_array_until(zval *return_value, HashTable *htbl, zend_fcall_info fci, zend_fcall_info_cache fci_cache, int stop_value) /* {{{ */
6269+
{
6270+
zval args[1];
6271+
zend_bool have_callback = 0;
6272+
zval *operand;
6273+
zval retval;
6274+
int result;
6275+
6276+
if (zend_hash_num_elements(htbl) == 0) {
6277+
RETURN_BOOL(!stop_value);
6278+
}
6279+
6280+
if (ZEND_FCI_INITIALIZED(fci)) {
6281+
have_callback = 1;
6282+
fci.retval = &retval;
6283+
fci.param_count = 1;
6284+
fci.params = args;
6285+
}
6286+
6287+
ZEND_HASH_FOREACH_VAL(htbl, operand) {
6288+
if (have_callback) {
6289+
zend_bool retval_true;
6290+
ZVAL_COPY(&args[0], operand);
6291+
6292+
/* Treat the operand like an array of size 1 */
6293+
result = zend_call_function(&fci, &fci_cache);
6294+
zval_ptr_dtor(operand);
6295+
if (result == FAILURE) {
6296+
return;
6297+
}
6298+
retval_true = zend_is_true(&retval);
6299+
zval_ptr_dtor(&retval);
6300+
/* The user-provided callback rarely returns refcounted values. */
6301+
if (retval_true == stop_value) {
6302+
RETURN_BOOL(stop_value);
6303+
}
6304+
} else {
6305+
if (zend_is_true(operand) == stop_value) {
6306+
RETURN_BOOL(stop_value);
6307+
}
6308+
}
6309+
} ZEND_HASH_FOREACH_END();
6310+
6311+
RETURN_BOOL(!stop_value);
6312+
}
6313+
/* }}} */
6314+
6315+
typedef struct {
6316+
zval *obj;
6317+
int stop_value;
6318+
int result;
6319+
int found;
6320+
zend_long has_callback;
6321+
zend_fcall_info fci;
6322+
zend_fcall_info_cache fcc;
6323+
} php_iterator_until_info;
6324+
6325+
static int php_traversable_func_until(zend_object_iterator *iter, void *puser) /* {{{ */
6326+
{
6327+
zend_fcall_info fci;
6328+
zval retval;
6329+
php_iterator_until_info *until_info = (php_iterator_until_info*) puser;
6330+
int result;
6331+
zend_bool stop;
6332+
6333+
fci = until_info->fci;
6334+
if (ZEND_FCI_INITIALIZED(fci)) {
6335+
zval *operand = iter->funcs->get_current_data(iter);
6336+
fci.retval = &retval;
6337+
fci.param_count = 1;
6338+
/* Use the operand like an array of size 1 */
6339+
fci.params = operand;
6340+
fci.retval = &retval;
6341+
Z_TRY_ADDREF_P(operand);
6342+
result = zend_call_function(&fci, &until_info->fcc);
6343+
zval_ptr_dtor(operand);
6344+
if (result == FAILURE) {
6345+
until_info->result = FAILURE;
6346+
return ZEND_HASH_APPLY_STOP;
6347+
}
6348+
6349+
stop = zend_is_true(&retval) == until_info->stop_value;
6350+
zval_ptr_dtor(&retval);
6351+
} else {
6352+
stop = zend_is_true(iter->funcs->get_current_data(iter)) == until_info->stop_value;
6353+
}
6354+
if (stop) {
6355+
until_info->found = 1;
6356+
return ZEND_HASH_APPLY_STOP;
6357+
}
6358+
return ZEND_HASH_APPLY_KEEP;
6359+
}
6360+
/* }}} */
6361+
6362+
static inline void php_iterable_until(INTERNAL_FUNCTION_PARAMETERS, int stop_value) /* {{{ */
6363+
{
6364+
zval *input;
6365+
zend_fcall_info fci = empty_fcall_info;
6366+
zend_fcall_info_cache fci_cache = empty_fcall_info_cache;
6367+
6368+
ZEND_PARSE_PARAMETERS_START(1, 2)
6369+
Z_PARAM_ZVAL(input)
6370+
Z_PARAM_OPTIONAL
6371+
Z_PARAM_FUNC_OR_NULL(fci, fci_cache)
6372+
ZEND_PARSE_PARAMETERS_END();
6373+
6374+
switch (Z_TYPE_P(input)) {
6375+
case IS_ARRAY:
6376+
php_array_until(return_value, Z_ARRVAL_P(input), fci, fci_cache, stop_value);
6377+
return;
6378+
case IS_OBJECT:
6379+
if (instanceof_function(Z_OBJCE_P(input), zend_ce_traversable)) {
6380+
php_iterator_until_info until_info;
6381+
6382+
until_info.obj = input;
6383+
until_info.fci = fci;
6384+
until_info.fcc = fci_cache;
6385+
6386+
until_info.stop_value = stop_value;
6387+
until_info.result = SUCCESS;
6388+
until_info.found = 0;
6389+
6390+
if (spl_iterator_apply(until_info.obj, php_traversable_func_until, (void*)&until_info) == SUCCESS && until_info.result == SUCCESS) {
6391+
RETURN_BOOL(!(until_info.found ^ stop_value));
6392+
}
6393+
return;
6394+
}
6395+
/* fallthrough */
6396+
default:
6397+
zend_argument_type_error(1, "must be of type iterable, %s given", zend_zval_type_name(input));
6398+
RETURN_THROWS();
6399+
break;
6400+
}
6401+
}
6402+
6403+
/* {{{ proto bool all(array input, ?callable predicate = null)
6404+
Determines whether the predicate holds for all elements in the array */
6405+
PHP_FUNCTION(all)
6406+
{
6407+
php_iterable_until(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
6408+
}
6409+
/* }}} */
6410+
6411+
/* {{{ proto bool any(array input, ?callable predicate = null)
6412+
Determines whether the predicate holds for at least one element in the array */
6413+
PHP_FUNCTION(any)
6414+
{
6415+
php_iterable_until(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
6416+
}
6417+
/* }}} */

ext/standard/basic_functions.stub.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,10 @@ function array_map(?callable $callback, array $array, array ...$arrays): array {
238238
/** @param string|int $key */
239239
function array_key_exists($key, array $array): bool {}
240240

241+
function any(iterable $iterable, ?callable $callback = null): bool {}
242+
243+
function all(iterable $iterable, ?callable $callback = null): bool {}
244+
241245
/**
242246
* @param string|int $key
243247
* @alias array_key_exists

ext/standard/basic_functions_arginfo.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: 4edb7cad23ccb051dbe267b3979e98892607c98f */
2+
* Stub hash: 7254e1bfd2ef7060f6682144462d4a9221205aa6 */
33

44
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0)
55
ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0)
@@ -347,6 +347,13 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_array_key_exists, 0, 2, _IS_BOOL
347347
ZEND_ARG_TYPE_INFO(0, array, IS_ARRAY, 0)
348348
ZEND_END_ARG_INFO()
349349

350+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_any, 0, 1, _IS_BOOL, 0)
351+
ZEND_ARG_TYPE_INFO(0, iterable, IS_ITERABLE, 0)
352+
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, callback, IS_CALLABLE, 1, "null")
353+
ZEND_END_ARG_INFO()
354+
355+
#define arginfo_all arginfo_any
356+
350357
#define arginfo_key_exists arginfo_array_key_exists
351358

352359
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_array_chunk, 0, 2, IS_ARRAY, 0)
@@ -2307,6 +2314,8 @@ ZEND_FUNCTION(array_reduce);
23072314
ZEND_FUNCTION(array_filter);
23082315
ZEND_FUNCTION(array_map);
23092316
ZEND_FUNCTION(array_key_exists);
2317+
ZEND_FUNCTION(any);
2318+
ZEND_FUNCTION(all);
23102319
ZEND_FUNCTION(array_chunk);
23112320
ZEND_FUNCTION(array_combine);
23122321
ZEND_FUNCTION(base64_encode);
@@ -2930,6 +2939,8 @@ static const zend_function_entry ext_functions[] = {
29302939
ZEND_FE(array_filter, arginfo_array_filter)
29312940
ZEND_FE(array_map, arginfo_array_map)
29322941
ZEND_FE(array_key_exists, arginfo_array_key_exists)
2942+
ZEND_FE(any, arginfo_any)
2943+
ZEND_FE(all, arginfo_all)
29332944
ZEND_FALIAS(key_exists, array_key_exists, arginfo_key_exists)
29342945
ZEND_FE(array_chunk, arginfo_array_chunk)
29352946
ZEND_FE(array_combine, arginfo_array_combine)
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
--TEST--
2+
Test all() function
3+
--FILE--
4+
<?php
5+
/*
6+
Prototype: bool all(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+
function dump_all(...$args) {
18+
try {
19+
var_dump(all(...$args));
20+
} catch (Error $e) {
21+
printf("Caught %s: %s\n", $e::class, $e->getMessage());
22+
}
23+
}
24+
25+
dump_all();
26+
dump_all(true);
27+
dump_all([]);
28+
dump_all(true, function () {});
29+
dump_all([], true);
30+
31+
echo "\n*** Testing basic functionality ***\n";
32+
33+
dump_all([1, 2, 3], 'is_int_ex');
34+
dump_all(['hello', 1, 2, 3], 'is_int_ex');
35+
$iterations = 0;
36+
dump_all(['hello', 1, 2, 3], function($item) use (&$iterations) {
37+
++$iterations;
38+
return is_int($item);
39+
});
40+
var_dump($iterations);
41+
42+
echo "\n*** Testing edge cases ***\n";
43+
44+
dump_all([], 'is_int_ex');
45+
46+
echo "\nDone";
47+
?>
48+
--EXPECTF--
49+
*** Testing not enough or wrong arguments ***
50+
Caught ArgumentCountError: all() expects at least 1 parameter, 0 given
51+
Caught TypeError: all(): Argument #1 ($iterable) must be of type iterable, bool given
52+
bool(true)
53+
Caught TypeError: all(): Argument #1 ($iterable) must be of type iterable, bool given
54+
Caught TypeError: all(): Argument #2 ($callback) must be a valid callback, no array or string given
55+
56+
*** Testing basic functionality ***
57+
bool(true)
58+
bool(false)
59+
bool(false)
60+
int(1)
61+
62+
*** Testing edge cases ***
63+
bool(true)
64+
65+
Done
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
--TEST--
2+
Test all() function
3+
--FILE--
4+
<?php
5+
/*
6+
Prototype: bool all(array $array, ?callable $callback = null, int $use_type = 0);
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+
function dump_all(...$args) {
16+
try {
17+
var_dump(all(...$args));
18+
} catch (Error $e) {
19+
printf("Caught %s: %s\n", $e::class, $e->getMessage());
20+
}
21+
}
22+
23+
24+
echo "\n*** Testing not enough or wrong arguments ***\n";
25+
26+
dump_all(new ArrayIterator());
27+
dump_all(new ArrayIterator(), true);
28+
29+
echo "\n*** Testing basic functionality ***\n";
30+
31+
dump_all(new ArrayIterator([1, 2, 3]), 'is_int_ex');
32+
dump_all(new ArrayIterator(['hello', 1, 2, 3]), 'is_int_ex');
33+
$iterations = 0;
34+
dump_all(new ArrayIterator(['hello', 1, 2, 3]), function($item) use (&$iterations) {
35+
++$iterations;
36+
return is_int($item);
37+
});
38+
var_dump($iterations);
39+
40+
echo "\n*** Testing edge cases ***\n";
41+
42+
dump_all(new ArrayIterator(), 'is_int_ex');
43+
44+
echo "\nDone";
45+
?>
46+
--EXPECTF--
47+
*** Testing not enough or wrong arguments ***
48+
bool(true)
49+
Caught TypeError: all(): Argument #2 ($callback) must be a valid callback, no array or string given
50+
51+
*** Testing basic functionality ***
52+
bool(true)
53+
bool(false)
54+
bool(false)
55+
int(1)
56+
57+
*** Testing edge cases ***
58+
bool(true)
59+
60+
Done

0 commit comments

Comments
 (0)