Skip to content

RFC: array_find #14108

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

Merged
merged 15 commits into from
May 31, 2024
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -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().
Expand Down
159 changes: 159 additions & 0 deletions ext/standard/array.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyone have an objection to making this ZEND_API?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no disagreement. I have chosen a lower visibility for the time being, as it is always easier to increase visibility afterwards instead of reducing it. But if you see a more global use case for the method, we can make it ZEND_API.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've now merged this as-is. The PHPAPI can be added in a follow-up commit if necessary/desired.

{
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)
{
Expand Down
8 changes: 8 additions & 0 deletions ext/standard/basic_functions.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}

/**
Expand Down
24 changes: 23 additions & 1 deletion ext/standard/basic_functions_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

86 changes: 86 additions & 0 deletions ext/standard/tests/array/array_all_basic.phpt
Original file line number Diff line number Diff line change
@@ -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)
Loading