1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

RFC: array_find (#14108)

see https://wiki.php.net/rfc/array_find
This commit is contained in:
Joshua Rüsweg
2024-05-31 19:39:12 +02:00
committed by GitHub
parent a58c3a7eb1
commit e4a8d5b16f
10 changed files with 757 additions and 1 deletions

1
NEWS
View File

@@ -283,6 +283,7 @@ PHP NEWS
as the magic $http_response_header variable.
. Add php_base64_encode_ex() API. (Remi)
. Implemented "Raising zero to the power of negative number" RFC. (Jorg Sowa)
. Added array_find(), array_find_key(), array_all(), and array_any(). (josh)
- XML:
. Added XML_OPTION_PARSE_HUGE parser option. (nielsdos)

View File

@@ -566,6 +566,9 @@ PHP 8.4 UPGRADE NOTES
http_clear_last_response_headers() that allows retrieving the same content
as the magic $http_response_header variable.
. Added function fpow() following rules of IEEE 754.
. Added functions array_find(), array_find_key(), array_all(), and
array_any().
RFC: https://wiki.php.net/rfc/array_find
- XSL:
. Added XSLTProcessor::registerPhpFunctionNS().

View File

@@ -6580,6 +6580,165 @@ PHP_FUNCTION(array_filter)
}
/* }}} */
/* {{{ Internal function to find an array element for a user closure. */
static zend_result php_array_find(const HashTable *array, zend_fcall_info fci, zend_fcall_info_cache fci_cache, zval *result_key, zval *result_value, bool negate_condition)
{
zend_ulong num_key;
zend_string *str_key;
zval retval;
zval args[2];
zval *operand;
if (result_value != NULL) {
ZVAL_UNDEF(result_value);
}
if (result_key != NULL) {
ZVAL_UNDEF(result_key);
}
if (zend_hash_num_elements(array) == 0) {
return SUCCESS;
}
ZEND_ASSERT(ZEND_FCI_INITIALIZED(fci));
fci.retval = &retval;
fci.param_count = 2;
fci.params = args;
ZEND_HASH_FOREACH_KEY_VAL(array, num_key, str_key, operand) {
/* Set up the key */
if (!str_key) {
ZVAL_LONG(&args[1], num_key);
} else {
ZVAL_STR_COPY(&args[1], str_key);
}
ZVAL_COPY(&args[0], operand);
zend_result result = zend_call_function(&fci, &fci_cache);
if (EXPECTED(result == SUCCESS)) {
int retval_true;
retval_true = zend_is_true(&retval);
zval_ptr_dtor(&retval);
/* This negates the condition, if negate_condition is true. Otherwise it does nothing with `retval_true`. */
retval_true ^= negate_condition;
if (retval_true) {
if (result_value != NULL) {
ZVAL_COPY(result_value, &args[0]);
}
if (result_key != NULL) {
ZVAL_COPY(result_key, &args[1]);
}
zval_ptr_dtor(&args[0]);
zval_ptr_dtor(&args[1]);
return SUCCESS;
}
}
zval_ptr_dtor(&args[0]);
zval_ptr_dtor(&args[1]);
if (UNEXPECTED(result != SUCCESS)) {
return FAILURE;
}
} ZEND_HASH_FOREACH_END();
return SUCCESS;
}
/* }}} */
/* {{{ Search within an array and returns the first found element value. */
PHP_FUNCTION(array_find)
{
zval *array = NULL;
zend_fcall_info fci;
zend_fcall_info_cache fci_cache = empty_fcall_info_cache;
ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_ARRAY(array)
Z_PARAM_FUNC(fci, fci_cache)
ZEND_PARSE_PARAMETERS_END();
if (php_array_find(Z_ARR_P(array), fci, fci_cache, NULL, return_value, false) != SUCCESS) {
RETURN_THROWS();
}
if (Z_TYPE_P(return_value) == IS_UNDEF) {
RETURN_NULL();
}
}
/* }}} */
/* {{{ Search within an array and returns the first found element key. */
PHP_FUNCTION(array_find_key)
{
zval *array = NULL;
zend_fcall_info fci;
zend_fcall_info_cache fci_cache = empty_fcall_info_cache;
ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_ARRAY(array)
Z_PARAM_FUNC(fci, fci_cache)
ZEND_PARSE_PARAMETERS_END();
if (php_array_find(Z_ARR_P(array), fci, fci_cache, return_value, NULL, false) != SUCCESS) {
RETURN_THROWS();
}
if (Z_TYPE_P(return_value) == IS_UNDEF) {
RETURN_NULL();
}
}
/* }}} */
/* {{{ Search within an array and returns true if an element is found. */
PHP_FUNCTION(array_any)
{
zval *array = NULL;
zend_fcall_info fci;
zend_fcall_info_cache fci_cache = empty_fcall_info_cache;
ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_ARRAY(array)
Z_PARAM_FUNC(fci, fci_cache)
ZEND_PARSE_PARAMETERS_END();
if (php_array_find(Z_ARR_P(array), fci, fci_cache, return_value, NULL, false) != SUCCESS) {
RETURN_THROWS();
}
RETURN_BOOL(Z_TYPE_P(return_value) != IS_UNDEF);
}
/* }}} */
/* {{{ Search within an array and returns true if an element is found. */
PHP_FUNCTION(array_all)
{
zval *array = NULL;
zend_fcall_info fci;
zend_fcall_info_cache fci_cache = empty_fcall_info_cache;
ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_ARRAY(array)
Z_PARAM_FUNC(fci, fci_cache)
ZEND_PARSE_PARAMETERS_END();
if (php_array_find(Z_ARR_P(array), fci, fci_cache, return_value, NULL, true) != SUCCESS) {
RETURN_THROWS();
}
RETURN_BOOL(Z_TYPE_P(return_value) == IS_UNDEF);
}
/* }}} */
/* {{{ Applies the callback to the elements in given arrays. */
PHP_FUNCTION(array_map)
{

View File

@@ -1897,6 +1897,14 @@ function array_reduce(array $array, callable $callback, mixed $initial = null):
function array_filter(array $array, ?callable $callback = null, int $mode = 0): array {}
function array_find(array $array, callable $callback): mixed {}
function array_find_key(array $array, callable $callback): mixed {}
function array_any(array $array, callable $callback): bool {}
function array_all(array $array, callable $callback): bool {}
function array_map(?callable $callback, array $array, array ...$arrays): array {}
/**

View File

@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 5d8e13990ce18bebc9c7e6a0a9a7ad8b7593d35b */
* Stub hash: 7d588414e84ed62088cd5b1c668b29c0e51d406f */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0)
ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0)
@@ -336,6 +336,20 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_array_filter, 0, 1, IS_ARRAY, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_LONG, 0, "0")
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_array_find, 0, 2, IS_MIXED, 0)
ZEND_ARG_TYPE_INFO(0, array, IS_ARRAY, 0)
ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0)
ZEND_END_ARG_INFO()
#define arginfo_array_find_key arginfo_array_find
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_array_any, 0, 2, _IS_BOOL, 0)
ZEND_ARG_TYPE_INFO(0, array, IS_ARRAY, 0)
ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0)
ZEND_END_ARG_INFO()
#define arginfo_array_all arginfo_array_any
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_array_map, 0, 2, IS_ARRAY, 0)
ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 1)
ZEND_ARG_TYPE_INFO(0, array, IS_ARRAY, 0)
@@ -2405,6 +2419,10 @@ ZEND_FUNCTION(array_sum);
ZEND_FUNCTION(array_product);
ZEND_FUNCTION(array_reduce);
ZEND_FUNCTION(array_filter);
ZEND_FUNCTION(array_find);
ZEND_FUNCTION(array_find_key);
ZEND_FUNCTION(array_any);
ZEND_FUNCTION(array_all);
ZEND_FUNCTION(array_map);
ZEND_FUNCTION(array_key_exists);
ZEND_FUNCTION(array_chunk);
@@ -3030,6 +3048,10 @@ static const zend_function_entry ext_functions[] = {
ZEND_RAW_FENTRY("array_product", zif_array_product, arginfo_array_product, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL)
ZEND_FE(array_reduce, arginfo_array_reduce)
ZEND_FE(array_filter, arginfo_array_filter)
ZEND_FE(array_find, arginfo_array_find)
ZEND_FE(array_find_key, arginfo_array_find_key)
ZEND_FE(array_any, arginfo_array_any)
ZEND_FE(array_all, arginfo_array_all)
ZEND_FE(array_map, arginfo_array_map)
ZEND_RAW_FENTRY("array_key_exists", zif_array_key_exists, arginfo_array_key_exists, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL)
ZEND_RAW_FENTRY("key_exists", zif_array_key_exists, arginfo_key_exists, 0, NULL, NULL)

View File

@@ -0,0 +1,86 @@
--TEST--
Test array_all() function : basic functionality
--FILE--
<?php
$array1 = [
"a" => 1,
"b" => 2,
"c" => 3,
"d" => 4,
"e" => 5,
];
$array2 = [
1, 2, 3, 4, 5
];
function even($input) {
return $input % 2 === 0;
}
class SmallerTenClass {
public static function smallerTen($input) {
return $input < 10;
}
}
var_dump(array_all($array1, fn($value) => $value > 0));
var_dump(array_all($array2, fn($value) => $value > 0));
var_dump(array_all($array2, fn($value) => $value > 1));
var_dump(array_all([], fn($value) => true));
echo '*** Test Exception after false result ***' . PHP_EOL;
try {
var_dump(array_all($array2, function ($value) {
if ($value > 1) {
throw new Exception("Test-Exception");
}
return false;
}));
} catch (Exception) {
var_dump("Unexpected Exception");
}
echo '*** Test aborting with exception ***' . PHP_EOL;
try {
var_dump(array_all($array2, function ($value) {
if ($value === 2) {
throw new Exception("Test-Exception");
}
var_dump($value);
return true;
}));
} catch (Exception) {
var_dump("Catched Exception");
}
var_dump(array_all($array1, 'even'));
var_dump(array_all($array1, function($value) {
// return nothing
}));
var_dump(array_all($array1, [
'SmallerTenClass',
'smallerTen'
]));
var_dump(array_all($array1, "SmallerTenClass::smallerTen"));
?>
--EXPECT--
bool(true)
bool(true)
bool(false)
bool(true)
*** Test Exception after false result ***
bool(false)
*** Test aborting with exception ***
int(1)
string(17) "Catched Exception"
bool(false)
bool(false)
bool(true)
bool(true)

View File

@@ -0,0 +1,91 @@
--TEST--
Test array_any() function : basic functionality
--FILE--
<?php
$array1 = [
"a" => 1,
"b" => 2,
"c" => 3,
"d" => 4,
"e" => 5,
];
$array2 = [
1, 2, 3, 4, 5
];
function even($input) {
return $input % 2 === 0;
}
class EvenClass {
public static function even($input) {
return $input % 2 === 0;
}
}
var_dump(array_any($array1, fn($value) => $value > 3));
var_dump(array_any($array2, fn($value) => $value > 3));
var_dump(array_any($array2, fn($value) => $value > 5));
var_dump(array_any([], fn($value) => true));
var_dump(array_any($array1, fn($value, $key) => $key === "c"));
var_dump(array_any($array1, fn($value, $key) => false));
echo '*** Test Exception after true result ***' . PHP_EOL;
try {
var_dump(array_any($array2, function ($value) {
if ($value > 1) {
throw new Exception("Test-Exception");
}
return true;
}));
} catch (Exception) {
var_dump("Unexpected Exception");
}
echo '*** Test aborting with exception ***' . PHP_EOL;
try {
var_dump(array_any($array2, function ($value) {
if ($value === 2) {
throw new Exception("Test-Exception");
}
var_dump($value);
return false;
}));
} catch (Exception) {
var_dump("Catched Exception");
}
var_dump(array_any($array1, 'even'));
var_dump(array_any($array1, function($value) {
// return nothing
}));
var_dump(array_any($array1, [
'EvenClass',
'even'
]));
var_dump(array_any($array1, "EvenClass::even"));
?>
--EXPECT--
bool(true)
bool(true)
bool(false)
bool(false)
bool(true)
bool(false)
*** Test Exception after true result ***
bool(true)
*** Test aborting with exception ***
int(1)
string(17) "Catched Exception"
bool(true)
bool(false)
bool(true)
bool(true)

View File

@@ -0,0 +1,90 @@
--TEST--
Test array_find() function : basic functionality
--FILE--
<?php
$array1 = [
"a" => 1,
"b" => 2,
"c" => 3,
"d" => 4,
"e" => 5,
];
$array2 = [
1, 2, 3, 4, 5
];
function even($input) {
return $input % 2 === 0;
}
class EvenClass {
public static function even($input) {
return $input % 2 === 0;
}
}
var_dump(array_find($array1, fn($value) => $value > 3));
var_dump(array_find($array2, fn($value) => $value > 3));
var_dump(array_find($array2, fn($value) => $value > 5));
var_dump(array_find([], fn($value) => true));
var_dump(array_find($array1, fn($value, $key) => $key === "c"));
var_dump(array_find($array1, fn($value, $key) => false));
echo '*** Test Exception after found result ***' . PHP_EOL;
try {
var_dump(array_find($array1, function ($value) {
if ($value > 1) {
throw new Exception("Test-Exception");
}
return true;
}));
} catch (Exception) {
var_dump("Unexpected Exception");
}
echo '*** Test aborting with exception ***' . PHP_EOL;
try {
var_dump(array_find($array2, function ($value) {
if ($value === 2) {
throw new Exception("Test-Exception");
}
var_dump($value);
return false;
}));
} catch (Exception) {
var_dump("Catched Exception");
}
var_dump(array_find($array1, 'even'));
var_dump(array_find($array1, function($value) {
// return nothing
}));
var_dump(array_find($array1, [
'EvenClass',
'even'
]));
var_dump(array_find($array1, "EvenClass::even"));
?>
--EXPECT--
int(4)
int(4)
NULL
NULL
int(3)
NULL
*** Test Exception after found result ***
int(1)
*** Test aborting with exception ***
int(1)
string(17) "Catched Exception"
int(2)
NULL
int(2)
int(2)

View File

@@ -0,0 +1,91 @@
--TEST--
Test array_find_key() function : basic functionality
--FILE--
<?php
$array1 = [
"a" => 1,
"b" => 2,
"c" => 3,
"d" => 4,
"e" => 5,
];
$array2 = [
1, 2, 3, 4, 5
];
function even($input) {
return $input % 2 === 0;
}
class EvenClass {
public static function even($input) {
return $input % 2 === 0;
}
}
var_dump(array_find_key($array1, fn($value) => $value > 3));
var_dump(array_find_key($array2, fn($value) => $value > 3));
var_dump(array_find_key($array2, fn($value) => $value > 5));
var_dump(array_find_key([], fn($value) => true));
var_dump(array_find_key($array1, fn($value, $key) => $key === "c"));
var_dump(array_find_key($array1, fn($value, $key) => false));
echo '*** Test Exception after found result ***' . PHP_EOL;
try {
var_dump(array_find_key($array1, function ($value) {
if ($value > 1) {
throw new Exception("Test-Exception");
}
return true;
}));
} catch (Exception) {
var_dump("Unexpected Exception");
}
echo '*** Test aborting with exception ***' . PHP_EOL;
try {
var_dump(array_find_key($array2, function ($value) {
if ($value === 2) {
throw new Exception("Test-Exception");
}
var_dump($value);
return false;
}));
} catch (Exception) {
var_dump("Catched Exception");
}
var_dump(array_find_key($array1, 'even'));
var_dump(array_find_key($array1, function($value) {
// return nothing
}));
var_dump(array_find_key($array1, [
'EvenClass',
'even'
]));
var_dump(array_find_key($array1, "EvenClass::even"));
?>
--EXPECT--
string(1) "d"
int(3)
NULL
NULL
string(1) "c"
NULL
*** Test Exception after found result ***
string(1) "a"
*** Test aborting with exception ***
int(1)
string(17) "Catched Exception"
string(1) "b"
NULL
string(1) "b"
string(1) "b"

View File

@@ -0,0 +1,205 @@
--TEST--
basic array_find test
--FILE--
<?php
$stdClass = new stdClass();
$stdClass->a = "1";
$array = [
...[0, 1, 2, -1, 034, 0X4A],
...[0.0, 1.2, 1.2e3, 1.2e-3],
...['value1', "value2", '', " ", ""],
...[true, false, TRUE, FALSE],
...[null, NULL],
...[1 => 'one', 'zero' => 0, -2 => "value"],
...["one" => 1, 5 => "float", true => 1, "" => 'empty'],
...[1 => 'one', 2, "key" => 'value'],
...[new stdClass(), $stdClass, (object)['b' => 2]],
...[[], [1, 2, 3], [-1 => 1, -2 => 2]],
...[@$undefined_var]
];
echo "*** Dumping each key value pair. ***". PHP_EOL;
var_dump(array_find($array, function ($value, $key) {
var_dump($key, $value);
return false;
}));
echo "*** Dumping only first key value pair. ***". PHP_EOL;
var_dump(array_find($array, function ($value, $key) {
var_dump($key, $value);
return true;
}));
echo "*** Returning each value. ***". PHP_EOL;
foreach (array_keys($array) as $key) {
var_dump(array_find($array, function ($value, $arrayKey) use ($key) {
return $arrayKey === $key;
}));
}
?>
--EXPECT--
*** Dumping each key value pair. ***
int(0)
int(0)
int(1)
int(1)
int(2)
int(2)
int(3)
int(-1)
int(4)
int(28)
int(5)
int(74)
int(6)
float(0)
int(7)
float(1.2)
int(8)
float(1200)
int(9)
float(0.0012)
int(10)
string(6) "value1"
int(11)
string(6) "value2"
int(12)
string(0) ""
int(13)
string(1) " "
int(14)
string(0) ""
int(15)
bool(true)
int(16)
bool(false)
int(17)
bool(true)
int(18)
bool(false)
int(19)
NULL
int(20)
NULL
int(21)
string(3) "one"
string(4) "zero"
int(0)
int(22)
string(5) "value"
string(3) "one"
int(1)
int(23)
string(5) "float"
int(24)
int(1)
string(0) ""
string(5) "empty"
int(25)
string(3) "one"
int(26)
int(2)
string(3) "key"
string(5) "value"
int(27)
object(stdClass)#2 (0) {
}
int(28)
object(stdClass)#1 (1) {
["a"]=>
string(1) "1"
}
int(29)
object(stdClass)#3 (1) {
["b"]=>
int(2)
}
int(30)
array(0) {
}
int(31)
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
int(32)
array(2) {
[-1]=>
int(1)
[-2]=>
int(2)
}
int(33)
NULL
NULL
*** Dumping only first key value pair. ***
int(0)
int(0)
int(0)
*** Returning each value. ***
int(0)
int(1)
int(2)
int(-1)
int(28)
int(74)
float(0)
float(1.2)
float(1200)
float(0.0012)
string(6) "value1"
string(6) "value2"
string(0) ""
string(1) " "
string(0) ""
bool(true)
bool(false)
bool(true)
bool(false)
NULL
NULL
string(3) "one"
int(0)
string(5) "value"
int(1)
string(5) "float"
int(1)
string(5) "empty"
string(3) "one"
int(2)
string(5) "value"
object(stdClass)#2 (0) {
}
object(stdClass)#1 (1) {
["a"]=>
string(1) "1"
}
object(stdClass)#3 (1) {
["b"]=>
int(2)
}
array(0) {
}
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
array(2) {
[-1]=>
int(1)
[-2]=>
int(2)
}
NULL