php: write php 8.4’s array_find from scratch

there’s an rfc vote currently underway for a number of new array functions in php8.4 (thanks to symfony station for pointing this out!). the proposal is for four new functions for finding and evaluating arrays using callables. the functions are:

  • array_find()
  • array_find_key()
  • array_any()
  • array_all()

the full details can be read here

if we’re impatient, though, we can skip waiting for php8.4 and homeroll these ourselves.

array_find()

at it’s heart, the functionality of array_find is array_filter. the major difference is that array_find returns only the first element of the array, not all of them.

with that in mind, we can build our own version like so:

/**
 * Find the first value in an array that evaluates to true in $func
 *
 * @param  array $array
 * @param  callable $func
 * @return any
 */
$array_find = function(array $array, callable $func) {
    return array_values(array_filter($array, $func))[0] ?? null;
};

when we run this against the test data given in the rfc, we get the expected results.

$array = [
    'a' => 'dog',
    'b' => 'cat',
    'c' => 'cow',
    'd' => 'duck',
    'e' => 'goose',
    'f' => 'elephant'
];

$array_find($array, function (string $value) {
    return strlen($value) > 4;
}); // string(5) "goose"

array_find_key()

the only difference between array_find_key and array_find is that the one that is called ‘find_key’ finds the key, not the value. no surprise.

we can implement this by taking our array_find code and adding array_key_first:

/**
 * Find the first key in an array where the value evaluates to true in $func
 *
 * @param  array $array
 * @param  callable $func
 * @return any
 */
$array_find_key = function(array $array, callable $func) {
    return array_key_first(array_filter($array, $func)) ?? null;
};

array_any() and array_all()

both array_any and array_all return booleans.

if one or more of the elements in the array passes the test in the callable, array_any will return true. we can achieve this by filtering the array and testing if the count of the result is greater than zero:

/**
 * Evaluate if any element in array $array evaluates to true in $func
 *
 * @param  array $array
 * @param  callable $func
 * @return bool
 */
$array_any = function(array $array, callable $func): bool {
    return (bool)count(array_filter($array, $func));
};

for array_all, all the elements in the array must pass the callable test. this requires us to test if the number of elements in the filtered array is the same as in the original array before filtering, ie. if all the elements passed the filter.

/**
 * Evaluate if all elements in array $array evaluates to true in $func
 *
 * @param  array $array
 * @param  callable $func
 * @return bool
 */
$array_all = function(array $array, callable $func): bool {
    return count(array_filter($array, $func)) == count($array);
};

and now we have a small slice of the power of php8.4 without the wait.

Posted by: grant horwood

co-founder of fruitbat studios. cli-first linux snob, metric evangelist, unrepentant longhair. all the music i like is objectively horrible. he/him.

Leave a Reply