はじめに
- YUZURIHAの松村です。
- 前回に続き、Laravelのアップデートでハマったポイントについて紹介します。
- 今回は、Laravelを
v12.23.0へアップデートした際にArr::firstメソッドでハマりました。
Arr::firstメソッドとは?
-
Arr::firstは、配列の中から指定した条件に最初に一致する要素を返すメソッドです。 - 具体的にどのように使うかは、Laravel公式の以下のページをご参照ください。
- 第一引数には、対象となる配列を指定します。
- 第二引数には、コールバック関数によって一致させる条件を指定します。
- 第二引数を指定しない場合は、配列の先頭にある要素が返ります。
Laravel v12.23.0でのArr::firstメソッドの変更
- Laravel
v12.23.0には、こちらのPRの変更が含まれています。
- まず、Laravelには
polyfillというライブラリが使われています。 - この
polyfillにより、PHPの新しいバージョンの関数を利用することができます。 - 例えば自分が開発しているLaravelプロジェクトのPHPがまだ8.2や8.3であっても、
polyfillによって8.4で新たに導入された関数を使えるというわけです。 - そしてこのPRの変更では、
polyfillを使うことによってPHP8.4で新たに導入された以下4つの配列に関するヘルパー関数を利用するようになりました。- array_find
- array_find_key
- array_any
- array_all
Arr::firstメソッドについては、array_find_key関数が利用されるようになりました。- PRの説明として「破壊的な変更がある」や「挙動が変わる」という旨の記述はどこにも無いため、あくまでも4つのヘルパー関数を使うようにしたリファクタリングのように見えます。
- しかし!実際に
v12.23.0にアップデートしてArr::firstメソッドを呼び出すと、挙動が変わっていました!😱ギャー
どのように挙動が変わったのか?
- 結論から言うと、挙動が変わってしまうのは以下2つの条件を両方とも満たしている場合でした。
- 第一引数に配列以外(Collectionやnull)を指定している
- 第二引数で条件を指定している
- 具体的にどのように挙動が変わるのか、
Tinkerを使って各パターンごとに見て行きます。
第一引数: Collection, 第二引数: あり
-
v12.22.1ではhogeが返ります。> Arr::first(collect(['hoge', 'fuga']), fn ($value) => $value == 'hoge'); = "hoge"
-
v12.23.0ではarray_find_key関数の引数でTypeErrorとなってしまいます。> Arr::first(collect(['hoge', 'fuga']), fn ($value) => $value == 'hoge'); TypeError array_find_key(): Argument #1 ($array) must be of type array, Illuminate\Support\Collection given, called in vendor/laravel/framework/src/Illuminate/Collections/Arr.php on line 266.
第一引数: Collection, 第二引数: なし
-
v12.22.1・v12.23.0ともに挙動は変わらず、hogeが返ります。> Arr::first(collect(['hoge', 'fuga'])); = "hoge"
第一引数: null, 第二引数: あり
-
v12.22.1ではワーニングになってnullが返ります。> Arr::first(null, fn ($value) => $value == 'hoge'); WARNING foreach() argument must be of type array|object, null given in vendor/laravel/framework/src/Illuminate/Collections/Arr.php on line 207. = null
-
v12.23.0ではarray_find_key関数の引数でTypeErrorとなってしまいます。> Arr::first(null, fn ($value) => $value == 'hoge'); TypeError array_find_key(): Argument #1 ($array) must be of type array, null given, called in vendor/laravel/framework/src/Illuminate/Collections/Arr.php on line 266.
第一引数: null, 第二引数: なし
-
v12.22.1・v12.23.0ともに挙動は変わらず、nullが返ります。> Arr::first(null); = null
第一引数: 配列, 第二引数: あり
-
v12.22.1・v12.23.0ともに挙動は変わらず、hogeが返ります。> Arr::first(['hoge', 'fuga'], fn ($value) => $value == 'hoge'); = "hoge"
第一引数: 配列, 第二引数: なし
-
v12.22.1・v12.23.0ともに挙動は変わらず、hogeが返ります。> Arr::first(['hoge', 'fuga']); = "hoge"
なぜ挙動が変わったのか?
- では、第一引数に配列以外(Collectionやnull)を指定して第二引数で条件を指定している場合、なぜ挙動が変わってしまったのでしょうか?
-
v12.22.1とv12.23.0のソースコードをそれぞれ確認して行きます。
v12.22.1
-
v12.22.1までは第二引数を指定した場合、第一引数をforeachでループして第二引数の$callbackによりチェックしていました。
v12.23.0
-
v12.23.0では第二引数を指定した場合、ループせずにarray_find_key関数にそのまま第一引数と第二引数を渡しています。
-
array_find_key関数の第一引数は、配列しか許可していません。
- そのためTypeErrorになったというわけですね。
正しい挙動はどうあるべきか
- 冒頭でも述べたように、
Arr::firstは配列の中から指定した条件に最初に一致する要素を返すメソッドです。 - そのため、そもそも配列ではないCollectionやnullを第一引数に指定するのはイレギュラーな使い方と考えられます。
- ただ
v12.23.0からの「第二引数の有無によって第一引数に指定して良いデータ型が変わる」というのは、混乱を招きやすいなと思いますね。。。 - それなら第二引数どうこうは関係なく、第一引数には配列のみを許可するようにデータ型を限定してもらいたいです。
最後に
- そもそもな話ですが、Collectionクラスには
first()というメソッドがちゃんと用意されています。
- Collectionインスタンスから
first()メソッドを呼び出し、引数に条件を指定することでその条件に一致する最初の要素を返してくれます。 -
Arr::first・Collection::firstと同じメソッド名であっても、どのクラスのメソッドを使うべきかを的確に判断することが大事ですね。