こんにちは、あきぞらです。
今日は、みんなが頭を悩ませている複雑なデータ取得について、
解決策「クエリスコープ」について紹介します。
クエリスコープで複雑なデータ取得が驚くほど簡単になります。
目次
クエリスコープとは?
クエリスコープは、よく使うクエリの一部をメソッドとしてモデルに定義する機能です。
つまり、複雑なクエリを再利用可能な小さな部品に分解できるんです。
例えば、こんな感じ。
public function scopeActive($query) { return $query->where('status', 'active'); }
これで、User::active()->get()
のように使えます。簡単ですね。
グローバルスコープとローカルスコープの違い
クエリスコープには、グローバルスコープとローカルスコープの2種類があります。
- グローバルスコープ:常に適用されるスコープ
- ローカルスコープ:必要なときだけ使うスコープ
グローバルスコープは、例えば「削除済みデータを常に除外する」ような場合に便利です。
一方、ローカルスコープは、より柔軟に使えるんです。
シンプルなクエリスコープの実装
さあ、実際にクエリスコープを書いてみましょう。
簡単なところから始めていきます。
基本的なスコープの書き方
クエリスコープを定義するのは、こんなに簡単です。
public function scopePopular($query) { return $query->where('views', '>', 1000); }
これで、人気のある記事だけを取得できますね。Article::popular()->get()
で使えます。
よく使われるスコープの例
実際のプロジェクトでよく使われるスコープをいくつか見てみましょう。
- 最近の投稿を取得
public function scopeRecent($query) { return $query->orderBy('created_at', 'desc'); }
- 特定のカテゴリの商品を取得
public function scopeCategory($query, $category) { return $query->where('category', $category); }
これらを組み合わせて使うこともできます。
例えば、Product::recent()->category('electronics')->get()
のように。
複雑なデータ取得のためのクエリスコープ
基本は押さえました。複雑なデータ取得をクエリスコープで簡単にしちゃいましょう。
複数の条件を組み合わせたスコープ
実際のアプリケーションでは、複数の条件を組み合わせることがよくあります。
例えば、「アクティブユーザーのうち、最近ログインした人」を取得したい場合。
public function scopeActiveAndRecentlyLoggedIn($query) { return $query->where('status', 'active') ->where('last_login_at', '>=', now()->subDays(7)); }
これで、User::activeAndRecentlyLoggedIn()->get()
と書くだけで、複雑な条件を満たすユーザーを簡単に取得できます。
リレーションを活用したスコープ
リレーションを使った複雑なクエリも、スコープを使えばスッキリ書けます。
public function scopeWithActiveComments($query) { return $query->whereHas('comments', function ($query) { $query->where('status', 'active'); }); }
これで、アクティブなコメントがついている投稿だけを取得できます。Post::withActiveComments()->get()
でOK!
動的パラメータを使用したクエリスコープ
クエリスコープの真の力を引き出すには、動的パラメータを使うのが鍵です。
これで、より柔軟で再利用性の高いスコープが作れます。
パラメータ付きスコープの実装方法
パラメータを受け取るスコープは、こんな感じで書きます:
public function scopeOlderThan($query, $age) { return $query->where('age', '>', $age); }
使うときは、User::olderThan(30)->get()
のように書けます。簡単ですね!
ユースケース:検索機能の実装
検索機能を実装する場合、クエリスコープを使うととても便利です:
public function scopeSearch($query, $term) { return $query->where(function ($query) use ($term) { $query->where('title', 'like', "%{$term}%") ->orWhere('content', 'like', "%{$term}%"); }); }
これで、Article::search('Laravel')->get()
と書くだけで、タイトルまたは本文にLaravelを含む記事を検索できます。
クエリスコープの組み合わせ技:レゴブロックのように組み立てる
クエリスコープの真髄は、組み合わせて使えること。
まるでレゴブロックのように、必要な部品を組み合わせて複雑なクエリを構築できるんです。
複数のスコープを連鎖させる方法
スコープは簡単に連鎖させられます:
$users = User::active() ->olderThan(25) ->withRecentPosts() ->get();
これで、アクティブで25歳以上、最近投稿があるユーザーを取得できます。読みやすいですよね?
スコープのオーバーライドとマージ
時には、既存のスコープを上書きしたり、マージしたりする必要があるかもしれません。
public function scopeActiveWithOverride($query, $status = 'active') { return $query->where('status', $status); }
これで、User::activeWithOverride('inactive')->get()
と書けば、非アクティブユーザーも取得できます。
パフォーマンス最適化・・・速度は正義
クエリスコープは便利ですが、使い方を間違えるとパフォーマンスに影響が出ることも。
ここでは、スコープを使いつつ高速なクエリを書くコツを紹介します。
N+1問題の回避
N+1問題は、データベースクエリのパフォーマンスを低下させる主な原因の一つです。
クエリスコープを使う際も、この問題に気をつける必要があります。
public function scopeWithAuthorAndComments($query) { return $query->with(['author', 'comments']); }
これを使えば、Post::withAuthorAndComments()->get()
で、投稿と一緒に著者とコメントを効率的に取得できます。
インデックスを考慮したスコープの設計
パフォーマンスを重視するなら、インデックスを考慮したスコープ設計が重要です:
public function scopeRecentlyActive($query) { return $query->where('last_active_at', '>=', now()->subDays(7)) ->orderBy('last_active_at', 'desc'); }
last_active_at
カラムにインデックスを貼っておけば、このスコープは高速に動作します。
テスト駆動開発(TDD)とクエリスコープ:品質を保証する
クエリスコープもテストの対象です。
TDDアプローチでスコープを開発すれば、より信頼性の高いコードが書けます。
スコープのユニットテスト
クエリスコープのテストは、こんな感じで書けます。
public function testActiveScope() { User::factory()->count(3)->create(['status' => 'active']); User::factory()->count(2)->create(['status' => 'inactive']); $activeUsers = User::active()->get(); $this->assertCount(3, $activeUsers); $this->assertTrue($activeUsers->every(fn ($user) => $user->status === 'active')); }
テスタブルなスコープの書き方
テストしやすいスコープを書くコツは、依存関係を注入可能にすることです:
public function scopeCreatedBetween($query, $start, $end) { return $query->whereBetween('created_at', [$start, $end]); }
これなら、テスト時に任意の日付範囲を指定できます。
実際の応用例など
Eコマースサイトと、ソーシャルメディアの例を見ていきます。
Eコマースサイトでの商品検索
Eコマースサイトでの複雑な商品検索を、クエリスコープを使って実装する例です。
class Product extends Model { public function scopeInStock($query) { return $query->where('stock', '>', 0); } public function scopeInCategory($query, $category) { return $query->where('category_id', $category); } public function scopePriceRange($query, $min, $max) { return $query->whereBetween('price', [$min, $max]); } public function scopeSearch($query, $term) { return $query->where('name', 'like', "%{$term}%") ->orWhere('description', 'like', "%{$term}%"); } }
これらのスコープを組み合わせれば、複雑な検索条件も簡単に表現できます。
$products = Product::inStock() ->inCategory(5) ->priceRange(1000, 5000) ->search('smartphone') ->get();
ソーシャルメディアのフィード表示
次に、ソーシャルメディアのフィード表示を例に取ってみましょう:
class Post extends Model { public function scopeFromFollowedUsers($query, User $user) { return $query->whereIn('user_id', $user->following()->pluck('id')); } public function scopePopular($query) { return $query->where('likes_count', '>', 100); } public function scopeRecent($query) { return $query->where('created_at', '>=', now()->subDays(7)); } public function scopeWithMedia($query) { return $query->has('media'); } }
これらのスコープを使えば、ユーザーごとにカスタマイズされたフィードを簡単に取得できます:
$feed = Post::fromFollowedUsers($currentUser) ->popular() ->recent() ->withMedia() ->get();
クエリスコープのベストプラクティス:プロの技を身につける
クエリスコープを使いこなすには、いくつかのベストプラクティスを押さえておくと良いでしょう。
ここでは、命名規則とドキュメンテーションについて見ていきます。
命名規則
クエリスコープの名前は、その機能を明確に表すものにしましょう。
一般的には以下のような規則が使われます。
- 動詞や形容詞で始める(例:
active()
,popular()
,recentlyUpdated()
) - パラメータを受け取るスコープは、その内容を名前に含める(例:
olderThan($age)
,inCategory($category)
) - 否定的な条件は
not
を使う(例:notVerified()
,excludeDeleted()
)
一貫性のある命名を心がければ、コードの可読性が大幅に向上しますよ。
ドキュメンテーションの重要性
クエリスコープにもドキュメントコメントを書くことをお勧めします。
特に、複雑な条件や副作用がある場合は必須です:
/** * スコープ:最近アクティブなユーザーを取得 * * @param \Illuminate\Database\Eloquent\Builder $query * @param int $days 遡る日数(デフォルト:7) * @return \Illuminate\Database\Eloquent\Builder */ public function scopeRecentlyActive($query, $days = 7) { return $query->where('last_active_at', '>=', now()->subDays($days)); }
これで、他の開発者(そして未来の自分)がこのスコープの機能を理解しやすくなります。