Eloquentのローカルスコープ解説：効率よくクエリを管理しよう

:::note 
<details><summary >AI要約</summary>

- PLaravelのEloquentにはローカルスコープとグローバルスコープがあり、この記事ではローカルスコープに焦点を当てる
- ローカルスコープはモデルのメソッドとして定義され、クエリの共通部分を再利用可能にする
- メリットにはコードの再利用性、可読性、メンテナンス性の向上があるが、過剰な抽象化や依存関係の複雑化のデメリットもある
- 定義したローカルスコープはモデルのクエリビルダ内で利用でき、アクティブユーザーの取得や特定のカテゴリのアイテム取得に使える
- ローカルスコープはモデルに対して定義され、そのモデルに基づくクエリに影響を与えるが、他のモデルから直接呼び出すことはできない
</details>
:::

## はじめに
LaravelでModelにクエリが集まったりして管理しにくいなーなんて思うようになって似たようなクエリを管理できるようにしたいと思った矢先にスコープという概念を知りました。
ローカルスコープ、グローバルスコープとありますが今回はローカルスコープについて解説していきたいと思います。

## 概要
Eloquentha,
Laravelの協力はORM（Object-Relational Mapping）で、データベース操作を簡単かつ直感的に行うことができます。
ローカルスコープは、クエ氏の共通部分を再利用可能なメソッドとしてモデルに定義する機能です。
これにより、クエリの再利用性とコードの可読性を向上させることができます。

## メリット、デメリット
### メリット
- コードの再利用
    - 共通のクエリロジックを１つのモデルファイルにまとめて定義することで、コードの重複を避けることができます
- コードの可読性向上
    - スコープを利用することで、クエリロジックが明示的にわかりやすくなり、コードの可読性が向上します
- メンテナンス性向上
    - クエリロジックが一箇所に集約されるため、変更が必要な場合でも、スコープの定義部分を変更するだけで済みます

### デメリット
- 過剰な抽象化リスク
    - スコープを多用しすぎると、クエリの実装が見えにくくなり、理解しづらくなる場合があります
- 依存関係の複雑化
    - スコープ間の依存関係が複雑になると、デバッグが難しくなる可能性があります

## 使い方
### 定義
ローカルスコープは、モデルクラスのメソッドとして定義します
メソッド名は`scope`という接頭辞を付け、その後にスコープ名を続けます
例えば`Active`というスコープを定義する場合、メソッド名は`scopeActive`となります

```php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Activeスコープを定義
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeActive($query)
    {
        return $query->where('active', 1);
    }
}
```

### スコープの使い方
定義したローカルスコープは、モデルクラスのクエリビルダ内でメソッドチェーンとして呼び出すことができます。
スコープメソッドの`scope`部分を**省略して**呼び出します

```php
$activeUsers = User::active()->get();
```

## 実際の開発現場で使われる場面
### アクティブユーザーの取得
上記の例のように、activeカラムが1のユーザーを取得するスコープを定義することで、アクティブユーザーの取得を簡潔に行うことができます

```php
$activeUsers = User::active()->get();
```

### 特定のカテゴリのアイテム取得
ECサイトなどで、特定のカテゴリに属する商品を取得するためのスコープを定義することができます
```php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    /**
     * Categoryスコープを定義
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @param string $category
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeCategory($query, $category)
    {
        return $query->where('category', $category);
    }
}

// 使用例
$electronics = Product::category('Electronics')->get();
```

### 期限切れアイテムの除外
有効期限が設定されているデータに対して、期限切れのデータを除外するスコープを定義することができます
```php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Carbon\Carbon;

class Item extends Model
{
    /**
     * NotExpiredスコープを定義
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeNotExpired($query)
    {
        return $query->where('expires_at', '>', Carbon::now());
    }
}

// 使用例
$validItems = Item::notExpired()->get();
```

## 応用例
ローカルスコープはパラメータを受け取ることもできます
これにより、動的なクエリをスコープで実現することができます

### 例：価格範囲スコープの定義
```php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    /**
     * Price範囲スコープを定義
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @param float $min
     * @param float $max
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopePriceRange($query, $min, $max)
    {
        return $query->whereBetween('price', [$min, $max]);
    }
}
```

```php
$productsInRange = Product::priceRange(100, 500)->get(); // 引数を用いてスコープを呼び出す
```

この応用例は、特定の価格範囲に属する商品を動的に取得するためのスコープ定義と利用方法を示しています
これにより、柔軟なクエリを簡単に実装することができます

## 影響範囲
ローカルスコープはモデルに対して定義されるため、そのモデルに基づくすべてのクエリに影響を与えることができます
特定の条件を一貫して適用するため、モデルのデータ取得が統一されたルールで行われることを保証します
定義したモデルファイルの外からは使えないということですね

### 失敗例：PostsモデルからUserモデルで定義したスコープを呼び出す
```php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    // Postモデルで何らかのクエリを実行する際にUserモデルのスコープを使おうとする
    public function getActiveUsersPosts()
    {
        // エラーが発生するコード
        return User::active()->with('posts')->get();
    }
}
```

エラーメッセージ
```plaintext
Call to undefined method App\Models\User::active()
```

## まとめ
Eloquentのローカルスコープは、共通のクエリロジックを再利用可能なメソッドとしてモデルに定義するための機能です
これにより、コードの再利用性、可読性、メンテナンス性が向上します

## 参考リンク
https://readouble.com/laravel/9.x/ja/eloquent.html