Laravelには、コレクションという便利な機能があります。本ページをご覧になっている方は、少なくともその存在はご存じな方が多いことでしょう。
しかしながら、詳しく知らなくてもある程度のコードが書けてしまうものです。私も「あーはいはい、こういうことね!」的に、これまでなんとなく使ってきました。
Laravel全体に言えることですが、直感的に使えるので良くも悪くもこういったことが起こります。
Laravelを使えば必ずと言ってよいほど使うコレクションについて、知識を深めたい!……と私自身も常々思っていたこともあり、改めてコレクションについて調べまとめてみました。
何となく使えている方も、復習を兼ねてご覧になってみてはいかがでしょうか。何か一つでも発見があれば嬉しいです。
コレクションとは!?
概要
Laravelのコレクションは、PHPの配列を操作するための便利なラッパーです。
PHPの配列でも、慣れれば自由に操作・加工は可能です。……が、より便利に、直感的に使える用にしてくれているのがコレクションです。
コード例
実際にコード例を見てみましょう。例えば以下の様なデータがあったとします。
$data = [
['name' => 'John', 'age' => 25, 'city' => 'Tokyo'],
['name' => 'Jane', 'age' => 30, 'city' => 'Osaka'],
['name' => 'Steve', 'age' => 28, 'city' => 'Tokyo'],
['name' => 'Lucas', 'age' => 40, 'city' => 'Nagoya']
];
年齢が30歳未満の人物の名前を大文字にして取得してみましょう。
まずは配列を使用した例です。
$results = [];
foreach ($data as $item) {
if ($item['age'] < 30) {
$results[] = strtoupper($item['name']);
}
}
print_r($results);
まあ、普通ですね。
私は素のPHP歴の方が断然長いものですから、何ら不便に思うことなく、受け入れることが可能です。
これを、コレクションで実装してみましょう。
$collection = collect($data);
$results = $collection->filter(function ($item) {
return $item['age'] < 30;
})->map(function ($item) {
return strtoupper($item['name']);
})->values();
print_r($results->all());
4行目で年齢のフィルタリング、6行目で大文字に変換しています。->
で繋げるチェーンメソッドで処理を繋げ、とても直感的に書けています。
コード量は増えていますが、foreach
を使わないことで、意図が分かりやすい&今風な書き方になりますね。
こういった書き方は、JavaScriptでも同じように流行が移り変わっています。
コレクションを作る・使う
主に以下の様な場合に作成・利用することになります。
- 自分で作成
- collect()ヘルパー関数で作成
- Collectionクラスをnewして作成
- 戻り値
- Eloquentモデルの戻り値
collect()ヘルパー関数で作成
以下はcollect()
ヘルパー関数で、コレクションを作成し、sum()
メソッドで合計値を取得しています。
// collectヘルパー関数で、コレクションを作成
$collection = collect([1, 2, 3, 4]);
$sum = $collection->sum();
echo $sum; // 出力: 15
ちなみに最初のコード例で使っていたのもcollect()
でした。なぜなら簡潔に書けるからです。
Collectionクラスをnewして作成
先ほどと全く同じことを、Collection
クラスをnew
してやってみましょう。
// Collectionクラスを使う場合はuseする必要あり
use Illuminate\Support\Collection;
// new Collection()で、コレクションを作成
$collection = new Collection([1, 2, 3, 4, 5]);
$sum = $collection->sum();
echo $sum; // 出力: 15
ちょっとだけコード量が増え、面倒な記述になりました。
なので個人的には、Laravelフレームワーク内で使用する場合、collect()
メソッドしか使わないですね。
Eloquentモデルの戻り値
自分で作るのではなく、Laravelフレームワークを利用する上で必然的に使うケースもあります。
例えばEloquentモデルでは、get()
など多数のレコードを取得する際の戻り値がコレクションとして返ってきます。
以下例では、isEmpty()
メソッドで結果を判定しています。
// 複数の結果が入ったコレクションを取得する
$users = User::where('account_active', 1)->get();
if (!$users->isEmpty()) {
// $users コレクションにデータが存在する場合の処理
// 例: ユーザーデータを表示する
} else {
// $users コレクションが空の場合の処理
// 例: 「アクティブなユーザーはいません」と表示する
}
このような分岐は非常によく使いますね。
コレクションのメリット
さて、ここまでコレクションの基本的な部分を見てきました。
改めて、コレクションはどういうところにメリットがあるのでしょうか。
PHPに無い機能が使える
これまでにも書いた通り、コレクションはPHPのラッパーであり、実質的には上位互換です。
素のPHPが用意していない機能も、コレクションではメソッドとして定義されています。
つまり自分で作らなくてもお膳立てされている。しかもメンテナンスまでされていますから信頼性も高いです。
超絶級のエンジニアの方々が作ってくださっているので、自作メソッドより断然良い!
直感的な操作
Laravelのコレクションは、そのメソッド名によって、何を行うのかが明白になっています。
再掲しますが、以下の様にsum()
メソッドが何をするか、初見でも分かります。
// collectヘルパー関数で、コレクションを作成
$collection = collect([1, 2, 3, 4]);
$sum = $collection->sum();
echo $sum; // 出力: 15
これにより、コードを読む際の認知負担が大幅に減少します。特に、他の開発者が書いたコードや、自分が昔書いたコードを読む場面で役立ちます。
メソッドチェーンができる
コレクションのメソッドは基本的に別のコレクションを返すので、->
で繋げるメソッドチェーンが使えます。
以下の様なデータがあったとして、一番多く使われているスキルを取得するとします。
$data = [
['name' => 'John', 'skills' => ['PHP', 'JS', 'HTML']],
['name' => 'Jane', 'skills' => ['JS', 'CSS', 'HTML']],
['name' => 'Steve', 'skills' => ['Python', 'JS', 'SQL']],
['name' => 'Lucas', 'skills' => ['Ruby', 'Rust', 'Go']]
];
こんな風にできるのは楽すぎます。
$collection = collect($data);
$mostCommonSkill = $collection->flatMap(function ($user) {
return $user['skills'];
})->countBy()->sort()->reverse()->keys()->first();
// 出力: Most common skill is: JS
echo "Most common skill is: " . $mostCommonSkill;
これを素の配列でやったらめちゃくちゃ面倒な気分になりますね。
先ほども触れたとおり、何をやってるかも明確です。
拡張性がある
macro()
により、コレクションに新しい自作メソッドを追加することも可能です。
例えば以下のように実装・利用できます。
use Illuminate\Support\Collection;
Collection::macro('customFilter', function () {
return $this->filter(function ($item) {
// 自作フィルタリングロジック
});
});
$collection = collect([...]);
$filtered = $collection->customFilter();
とは言え、もともと非常に多くの便利なメソッドがあるので、あまり利用する機会が無い……。
Laravel外でも利用可能
コレクションは非常に便利です。Laravel以外でも使えないかなぁと誰もが思うところですが、実は使えます。
以下の様にComposerを利用してインストールし、
composer require illuminate/support
あとはuseすれば普通に使えます。もちろん、collect()
ヘルパー関数はありませんので、new
してあげることになります。
require 'vendor/autoload.php';
use Illuminate\Support\Collection;
$collection = new Collection([1, 2, 3, 4, 5]);
$squared = $collection->map(function ($item) {
return $item * $item;
});
print_r($squared->all());
または自作のcollect()
メソッドを作る手もありそうです。
コレクションのデメリット(というか注意点)
メリットを取り上げたのならデメリットも同じように解説する必要がある……。と言いたいところですが、特別デメリットといえるようなデメリットは無いように思います。
ですのでしいて言うなら……というものであり、デメリットというよりは「注意点」のようなイメージでお考えください。
パフォーマンス
Laravelのコレクションは、素の配列を拡張し、便利に使うことが可能です。
しかしその内側では、素のPHPの機能を利用して処理をしているのは同じです。であれば処理が多くなる分、素の配列操作よりもパフォーマンスが劣る可能性はあります。
例えば、以下のようなアプリケーションでは、この差が問題になる場面も考えられます。
- 大量のデータを頻繁に操作する場合
- リアルタイムの処理が必要な場合
- 高速なレスポンスが最優先な場合
実際のところ、差など微々たるものだと思います。多くのウェブアプリケーションで問題になることはないでしょう。
ですが、パフォーマンスがクリティカルな場面ではあえて使わない……という判断をするケースもあるのかもしれません。
普通は気にしなくてOKです。
専用の知識が必要になる
例えば、PHPの配列はPHPエンジニアであれば100人中100人、誰もが知っている機能であると思います。
加えて配列操作の関数は、多くの人がコードを読めることでしょう。
しかしながらLaravelのコレクションを知らない人は、配列を知らない人と比較して多いはずです。加えてメソッドはかなり大量にあります……。
ですので開発メンバーがコレクションを知らない・詳しくない場合は、問題が発生する可能性があります。学習のためのコストも必要になりますね。
私がよく使うメソッド
さて、基本的なコレクションの知識が身についたという方は、次にやるのはメソッド一覧を流し読みすることだと思います。
しかし140以上もあるので大変ですし、覚えられません。
ですからまずは流し読みして、後は必要時にマニュアルを調べるのが良いと思います。
とは言えそれでは参考にならないので、ここでは個人的によく使うメソッドをご紹介したいと思います。もしもあなたが使っていないメソッドがあれば参考にしてください。
isEmpty(), isNotEmpty()
先ほども軽く出てきましたが、これは定番中の定番です。
Eloquentモデルの、複数データを取得しようとした際、結果があるかどうかを判定します。
// DBから複数のデータを取得
$users = User::where('account_active', 1)->get();
if ($users->isEmpty()) {
// 結果が見つからない場合
} else {
// 結果が見つかった場合
}
if ($users->isNotEmpty()) {
// 結果が見つかった場合
} else {
// 結果が見つからない場合
}
すごく直感的ですよね。
否定である!
を使えば結果が逆になります。ですが少なくとも10行目のような「否定を否定」するような意味にとれるコードは控えた方が良さそうです。
// DBから複数のデータを取得
$users = User::where('account_active', 1)->get();
if (!$users->isEmpty()) {
// 結果が見つかった場合
} else {
// 結果が見つからない場合
}
if (!$users->isNotEmpty()) {
// 結果が見つからない場合
} else {
// 結果が見つかった場合
}
count()
コレクションのアイテム数を返します。Eloquentモデルから複数件取得する際、件数を確認するのに非常に便利です。
// データベースからアクティブなユーザーを取得
$users = User::where('account_active', 1)->get();
$userCount = $users->count();
echo $userCount; // 出力: アクティブなユーザーの数
get()
コレクションから指定したキーに対応する値を取得します。
$collection = collect(['name' => 'Taylor', 'age' => 30]);
$name = $collection->get('name');
// 結果: "Taylor"
echo $name;
指定したキーが存在しない場合、デフォルトの値を返すこともできます。Laravelらしいというか、地味にありがたいですね。
$collection = collect(['name' => 'Taylor', 'age' => 30]);
$gender = $collection->get('gender', 'Unknown');
// 結果: "Unknown" (gender はコレクションに存在しないため、デフォルトの 'Unknown' が返される)
echo $gender;
all()
コレクションを配列にして取得します。
コレクションのチェーンメソッドなどで組み合わせた後、最終結果を配列として取得する場面などでよく使用されます。
$collection = collect([
['id' => 1, 'name' => 'Alice', 'age' => 28],
['id' => 2, 'name' => 'Bob', 'age' => 22],
['id' => 3, 'name' => 'Charlie', 'age' => 25],
['id' => 4, 'name' => 'David', 'age' => 30]
]);
// 25歳以上のユーザーをフィルタリングして、その名前だけを最終的に配列で取得
$names = $collection->where('age', '>=', 25)
->pluck('name')
->all();
print_r($names);
// 出力: Array ( [0] => Alice [2] => Charlie [3] => David )
filter()
コレクションをフィルタリングするために使います。クロージャ(匿名関数)を引数に与え、返り値がtrueになるデータだけで構成されたコレクションが作られます。
言葉で言うよりコードの方が分かりやすいかもしれません。以下の例では、4行目の$value > 2
のデータのみのコレクションが作成されています。
$collection = collect([1, 2, 3, 4, 5]);
$filtered = $collection->filter(function ($value, $key) {
return $value > 2;
});
print_r($filtered->all()); // 出力: Array ( [2] => 3 [3] => 4 [4] => 5 )
こういうシンプルすぎる例だと返って分かりづらいこともあるのでもう一例。
以下はアクティブなアカウントを持つユーザーをフィルタリングして取得しています。
$users = collect([
['id' => 1, 'name' => 'Alice', 'account_active' => true],
['id' => 2, 'name' => 'Bob', 'account_active' => false],
['id' => 3, 'name' => 'Charlie', 'account_active' => true],
]);
$activeUsers = $users->filter(function ($user) {
return $user['account_active'];
});
print_r($activeUsers->all());
// 出力:
// Array (
// [0] => Array ( [id] => 1 [name] => Alice [account_active] => 1 )
// [2] => Array ( [id] => 3 [name] => Charlie [account_active] => 1 )
// )
これは直感的ですね。
map()
コレクション内の各データに対し指定したクロージャを適用し、その結果を持つ新しいコレクションを返します。以下は4行目でreturn
された値で新しいコレクションが作られていることがわかります。
$collection = collect([1, 2, 3, 4, 5]);
$mapped = $collection->map(function ($value, $key) {
return $value * 2;
});
print_r($mapped->all()); // 出力: Array ( [0] => 2 [1] => 4 [2] => 6 [3] => 8 [4] => 10 )
toJson()
コレクションを JSON 形式の文字列に変換します。そのままなので非常に分かりやすいメソッドで、APIで値を返す際等、利用頻度は高いです。
$collection = collect([
['name' => 'Taylor', 'age' => 30],
['name' => 'Abigail', 'age' => 28],
]);
$json = $collection->toJson();
echo $json;
// [{"name":"Taylor","age":30},{"name":"Abigail","age":28}]
dd(), dump()
Laravelの同名ヘルパー関数と同じです。dd()
はDump and Dieの略なのでプログラムの処理がそこで終了します。
$collection = collect(['name' => 'Taylor', 'framework' => 'Laravel']);
$collection->dd();
dump()
はダンプだけなので、その後も処理は続きます。
$collection = collect(['name' => 'Taylor', 'framework' => 'Laravel']);
$collection->dump();
// このコードも実行される
echo "This will still get printed.";
これ、わざわざ使うかなぁ
私もそう思っていたのですが、メソッドチェーンの途中で値を確認するなどでは有用です。
$collection = collect([1, 2, 3, 4, 5]);
$collection->map(function ($item) {
return $item * 2;
})->dump() // この時点でのコレクションの内容をダンプ
->filter(function ($item) {
return $item > 5;
})->dd(); // この時点でのコレクションの内容をダンプして実行を停止
これでコレクションの操作中に途中の結果を確認することができます。
デバッグ時などはありだと思います。
pluck()
コレクションから指定したキーの値だけを取り出します。
$collection = collect([
['name' => 'Taylor', 'age' => 30],
['name' => 'Abigail', 'age' => 28],
['name' => 'James', 'age' => 26],
]);
$names = $collection->pluck('name');
// 結果: ["Taylor", "Abigail", "James"]
print_r($names->all());
join()
コレクションを任意の文字列で結合します。要はPHPのimplode()
またはjoin()
ですね。
$collection = collect(['a', 'b', 'c']);
$result = $collection->join(', ');
// 結果: "a, b, c"
echo $result;
PHPのimplode()
はなにげによく使うので便利は便利なのですが、こちらのメソッドはさらに便利です。
以下の様に、最後の項目の前の結合文字列を指定したりも可能です。
$collection = collect(['a', 'b', 'c']);
$result = $collection->join(', ', ' and ');
// 結果: "a, b and c"
echo $result;
日本語圏で実際使うか……と言えばわかりませんが、いちいち(良い意味で)機能がついているので、すごいなぁと感じます。
keys()
コレクションの全てのキーを新しいコレクションとして返します。
$collection = collect([
'name' => 'Michel',
'framework' => 'Laravel',
'version' => '8.0',
]);
$keys = $collection->keys();
dd($keys);
// ["name", "framework", "version"]
まとめ
以上、コレクションについてまとめました。
今回まとめてみることで、自分的にはまあ普通に使えていたなという反面、メソッドほとんど知らないじゃん……ということが分かりました。
140種~もあるので、全然使えていないなと。
今回改めて全メソッドを流し読みしたので、次からは「こんな機能あったなぁ」と、調べやすくなった気がします。
まだ公式マニュアルを読んでない方は是非そちらもご覧ください。大変ですが、一度でも目に通しておくだけで大分違うように思います。
コメント