Laravelで開発をしていたある日・・・
きちんと動作していたコードの中の1行を、少し位置を変えただけでエラーになってしまうという現象が起きました。
例えば、「お知らせの最新3件を取得し、古い順に並べ替えたい」という目的で、コントローラーに以下のコードを書いていたとします。
// このコードは問題なく動作します
$informations = Information::with('author')
->take(3)
->get()
->sortBy('created_at');
上記のコードでは正常に動きますが、下記のコードのようにsortBy の位置を get() の前に移動すると、エラーが発生します。
// NG!エラーが出ます
$informations = Information::with('author')
->take(3)
->sortBy('created_at') // この位置ではエラーに
->get();
//BuilderクラスにsortBy()メソッドが存在しない、というエラーが表示される
Call to undefined method Illuminate\Database\Eloquent\Builder::sortBy()
このエラーに遭遇したときは「位置を変えただけなのになんでエラーになるの??」となりました。
なぜ、同じ sortBy メソッドなのに get() の前後で挙動が変わってしまうのでしょうか?
調べてみると、その原因はLaravelにおける Query Builder(クエリビルダー) と Collection(コレクション) という、2つの異なる状態が原因ということが分かりました。
クエリを組み立てる段階(Query Builder)と、データが取得された後の段階(Collection)で使えるメソッドが違う ため、エラーが起きていたのです。
そこで、この記事では、エラーについて調べていく中で分かったQuery Builder(クエリビルダー)とCollection(コレクション)の違いについて解説します。
🧐 なぜエラーが出るの? 2つのメソッドの正体
まず、今回のエラーのコードを見てみましょう。
❌ エラーが出るコード
// NG!エラーが出ます
$informations = Information::with('author')
->take(3)
->sortBy('created_at')
->get();
✅ 問題無く動くコード
// OK!正しく動きます
$informations = Information::with('author')
->take(3)
->get()
->sortBy('created_at');
どちらのコードも「お知らせの古い順の3件を取得する」という同じ目的ですが、なぜ片方はエラーになるのでしょうか?
その答えは、get() というメソッドを境に、コードが「Query Builder」から「Collection」へと変化しているからです。
先述したように、Query BuilderとCollectionそれぞれで使えるメソッドが違うため、エラーになってしまうのです。
🔍 Query Builder(クエリビルダー)とCollection(コレクション)ってなに?
自分も含めLaravelを学び始めたばかりの人にとって、この2つの単語は聞きなれないかもしれません。
ここでは、レストランでの注文を例に、2つの役割を分かりやすく説明します。
Query Builder:データベースへの「注文書」📝
Query Builderは、データベースから「どんなデータを、どんな条件で取得するか」を組み立てるためのツールです。
例えるなら、 「レストランの注文書」 です。
まだ料理(データ)が手元に届いていない状態で、「こういう料理をお願いします」とシェフ(データベース)に指示を出している段階です。
この段階では、主にデータベースで処理するメソッドを使います。
使えるメソッドの例
orderBy(): データベースでデータの並び順を指定take()/limit(): データベースで取得する件数を制限where(): データベースで検索条件を指定with(): 関連するデータも一緒に取得
Collection:手元に届いた「料理の皿」🥢
一方、Collectionは、get()メソッドを実行して実際にデータベースから取得したデータのことです。
例えるなら、 「テーブルに並べられた料理の皿」 です。
データがすでに手元にあるので、それをPHPのコードで好きなように並び替えたり、絞り込んだりといった加工ができます。
この段階では、PHPの機能を使ってデータを操作するメソッドを使います。
使えるメソッドの例
sortBy()/sortByDesc(): PHPでデータの並び替えfilter(): PHPで特定のデータを絞り込みmap(): PHPでデータの形を変換count(): PHPでデータの件数をカウント
💡 基本的にQuery Builder(クエリビルダー)を使うようにする
結果として同じデータを取得できますが、基本的には できる限りQuery Builderを使う方が良いです。
なぜなら、処理の効率が圧倒的に良いからです。
Query Builder(注文書)の場合
「新しい順の3件をください」とシェフに頼めば、シェフは必要な3件だけ作ってくれます。
データベースから取得するデータが少ないので、高速でメモリも節約できます。
// 新しい順の3件だけを取得する
Information::orderBy('created_at', 'desc')->take(3)->get();
Collection(料理の皿)の場合
「全部の料理をください」と頼んでから、手元に届いた大量の料理の中から、新しい順の3つだけを選ぶようなものです。
// 全件取得してから、手元で3件に絞り込み
Information::all()->sortByDesc('created_at')->take(3);
データベースに1万件のデータがあったら、まず1万件すべてのデータを取得し、その後で不要な9,997件を捨てることになります。これは無駄な通信とメモリ消費が発生し、パフォーマンスが低下します。
以上のことから、出来る限りQuery Builderを使う方が良いということがお分かりいただけたかと思います。
🧐 どんなときにCollectionを使うべき?
処理の効率を考えるとQuery Builderを優先すべきですが、Collectionで処理した方が良いケースも存在します。
それは、 データベースでは処理できないロジックを適用したい場合 です。
複雑な計算やアプリケーションのロジックを適用する場合
データベースは単純な値の比較や集計は得意ですが、複数のカラムを組み合わせた複雑な計算や、データベースとは別の場所にある情報を使った条件分岐は苦手です。
そのようなときにCollectionが役立ちます。
例:キャンペーンによる割引価格で絞り込む
あるオンラインストアで、priceとdiscount_rateというカラムを持つ商品データがあるとします。
しかし、discount_rateは0.1(10%オフ)や0.2(20%オフ)といった値で、実際の割引価格は price * (1 - discount_rate) という計算が必要です。
さらに、「割引後の価格が1,000円以下の商品だけを表示したい」という要件があるとします。
この計算式はデータベースのWHERE句に直接書くには複雑です。
そこで、一度全てのデータを取得し、PHP側で計算して絞り込みます。
コード例:
// 全ての商品データを取得
$products = Product::all();
// 割引後の価格が1,000円以下の商品だけを絞り込む
$discountedProducts = $products->filter(function ($product) {
// 💡 データベースではできない計算をPHPで行う
$finalPrice = $product->price * (1 - $product->discount_rate);
return $finalPrice <= 1000;
});
解説:
このコードは、Product::all()で全ての商品データをCollectionとして取得します。
次に、filter()メソッドを使って、それぞれの商品のpriceとdiscount_rateを使ってPHPで 動的に最終価格を計算 し、「1,000円以下」という条件に合うものだけを抽出しています。
このように、データベースの列を直接使えない複雑な計算や、独自のビジネスロジックを適用したい場合に、Collectionのメソッドが非常に有効です。
パフォーマンスの観点から推奨されるのはQuery Builderですが、柔軟性が必要な場面ではCollectionの出番となります。
違いを理解したうえで、正しく使い分けることが大切になりますね。
まとめ
| Query Builder(クエリビルダー) | Collection(コレクション) | |
|---|---|---|
| 役割 | データベースへの指示書を作る | 取得したデータをPHPで操作する |
| メソッド | orderBy(), where(), take() など | sortBy(), filter(), map() など |
| 境界線 | get() より前 | get() より後 |
| 効率 | ✅ 良い(データベースで処理) | ❌ 悪い(PHPで処理) |
この2つの違いを理解すれば、Laravelでのコーディングがぐっとスムーズになります。
エラーに遭遇したときも、まずは 「今、コードはQuery Builderの状態かな? Collectionの状態かな?」 と立ち止まって考えることが出来ますね。
解説した2つの違いを理解し、Laravelでのシステム開発にお役立てください。
それでは。