【PHP8.4】便利な配列検索関数array_find()が追加される

配列で存在確認する関数といえば、どこに行っても第三引数`true`を指定しろと書かれている[in_array](https://www.php.net/manual/ja/function.in-array.php)・[array_search](https://www.php.net/manual/ja/function.array-search.php)と、`isset($array[$key])`って書いた方が短い謎関数[array_key_exists](https://www.php.net/manual/ja/function.array-key-exists.php)くらいしかありませんでした。
いずれもまあそれなりに便利ではあるのですが、たとえば正規表現検索できないなど色々と使い勝手の足りないところがありました。

ということでコールバックで任意に検索できる関数を追加しようというRFCが提出されました。

既に受理されており、PHP8.4から使用可能になります。

以下は該当のRFC、[array_find](https://wiki.php.net/rfc/array_find)の日本語訳です。


# PHP RFC: array_find

## Introduction

このRFCでは、特定の条件に一致する要素が存在するかをチェックする新しい配列関数`array_find`・`array_find_key`・`array_any`・`array_all`を追加します。

コールバックを使って配列を処理する関数がいくつか存在します。
しかし、条件に一致する要素をコールバックを使って検索する関数はまだありません。
これらをユーザランドに実装するのは比較的簡単ですが、よく使われるため車輪の再発明になりがちです。
また、Rust・JavaScript・C++などでは既に言語として実装されています。
従って、これらの関数をPHPに実装する意義はあるでしょう。

また、関数の実装は[array_filter](http://www.php.net/array_filter)とよく似ているため、メンテナンスの労力も大きくありません。


## Proposal

以下の4関数、`array_find`・`array_find_key`・`array_any`・`array_all`を追加します。


### array_find

`array_find`は、引数`$callback`がtrueを返す最初の要素の値を返します。
一致する要素が存在しない場合はnullが返ります。

```php
function array_find(array $array, callable $callback): mixed {
    foreach ($array as $key => $value) {
        if ($callback($value, $key)) {
            return $value;
        }
    }
 
    return null;
}
```


以下は例です。

```php
$array = [
    'a' => 'dog',
    'b' => 'cat',
    'c' => 'cow',
    'd' => 'duck',
    'e' => 'goose',
    'f' => 'elephant'
];
 
// 5文字以上の値を検索
var_dump(array_find($array, function (string $value) {
    return strlen($value) > 4;
})); // string(5) "goose"
 
// fで始まる値を検索
var_dump(array_find($array, function (string $value) {
    return str_starts_with($value, 'f');
})); // NULL
 
// 1文字目がキーになっている値を検索
var_dump(array_find($array, function (string $value, $key) {
   return $value[0] === $key;
})); // string(3) "cow"
 
// 正規表現に一致するキーを検索
var_dump(array_find($array, function ($value, $key) {
   return preg_match('/^([a-f])$/', $key);
})); // string(3) "dog"
```


### array_find_key

`array_find`は、引数`$callback`がtrueを返す最初の要素のキーを返します。
一致する要素が存在しない場合はnullが返ります。

```php
function array_find_key(array $array, callable $callback): mixed {
    foreach ($array as $key => $value) {
        if ($callback($value, $key)) {
            return $key;
        }
    }
 
    return null;
}
```

以下は例です。

```php
// 5文字以上の値を検索してキーを返す
var_dump(array_find_key($array, function (string $value) {
    return strlen($value) > 4;
})); // string(1) "e"
 
// fで始まる値を検索してキーを返す
var_dump(array_find_key($array, function (string $value) {
    return str_starts_with($value, 'f');
})); // NULL
 
// 1文字目がキーになっている値を検索してキーを返す
var_dump(array_find_key($array, function (string $value, $key) {
   return $value[0] === $key;
})); // string(1) "c"
 
// 正規表現に一致するキーを検索してキーを返す
var_dump(array_find_key($array, function (string $value, $key) {
   return preg_match('/^([a-f])$/', $key);
})); // string(1) "a"
```


### array_any

`array_any`は、引数`$callback`がいずれかの要素に対してtrueを返す場合にtrueを返します。
そうでなければfalseを返します。

```php
function array_any(array $array, callable $callback): bool {
    foreach ($array as $key => $value) {
        if ($callback($value, $key)) {
            return true;
        }
    }
 
    return false;
}
```

以下は例です。

```php
// 5文字以上の値が存在するか
var_dump(array_any($array, function (string $value) {
    return strlen($value) > 5;
})); // bool(true)
 
// 3文字未満の値が存在するか
var_dump(array_any($array, function (string $value) {
    return strlen($value) < 3;
})); // bool(false)
 
// string型でないキーが存在するか
var_dump(array_any($array, function (string $value, $key) {
   return !is_string($key);
})); // bool(false)
```


### array_all

`array_all`は、引数`$callback`が全ての要素に対してtrueを返す場合にtrueを返します。
そうでなければfalseを返します。

```php
function array_all(array $array, callable $callback): bool {
    foreach ($array as $key => $value) {
        if (!$callback($value, $key)) {
            return false;
        }
    }
 
    return true;
}
```

以下は例です。

```php
// 全ての値が12文字未満か
var_dump(array_all($array, function (string $value) {
    return strlen($value) < 12;
})); // bool(true)
 

// 全ての値が6文字以上か
var_dump(array_all($array, function (string $value) {
    return strlen($value) > 5;
})); // bool(false)
 
// 全てのキーがstring型か
var_dump(array_all($array, function (string $value, $key) {
   return is_string($key);
})); // bool(true)
```


## Backward Incompatible Changes

下位互換性のない変更点。

ユーザランドで同名の関数を作っていた場合動作しなくなります。
GitHubで軽く検索したところ、`array_find`が656件、`array_find_key`が28件、`array_any`が127件、`array_all`が284件ありました。


## Proposed PHP Version(s)

PHP8.4

## RFC Impact

SAPIやエクステンションへの影響は特にありません。
新しい定数やini設定はありません。

## Open Issues

この関数は必ずコールバックにキーと値を渡します。
コールバックに渡すパラメータを設定するパラメータがある[array_filter](http://www.php.net/array_filter)とは動作が異なります。
キーを渡せないかわりに複数の配列を渡せる[array_map](https://www.php.net/array_map)とも動作が異なります。

## Unaffected PHP Functionality

このRFCは新しい関数を追加するだけであり、他のPHP機能に影響は与えません。


## Proposed Voting Choices

投票期間は2024/05/15～2024/05/29、投票の2/3の賛成で受理されます。

`array_find()/array_find_key()`は賛成21反対0、`array_any()/array_all()`は賛成20反対0の全会一致で可決されました。


## Implementation

https://github.com/php/php-src/pull/14108


# 感想

これまでこういうことをやる場合は素直にforeachでぐるぐる回すか、array_walkあたりで芸術的な記述をしていたわけですが、なんかいいかんじに書けるようになりますね。

まあ私はおそらく今後もforeach使うと思いますが。

ちなみに『引数`$callback`がtrueを返す全ての要素を返す』関数は[array_filter](https://www.php.net/manual/ja/function.array-filter.php)です。
名前が揃ってないのがそこはかとなくわかりにくい。