【Laravel】ローカルスコープを使ってみる

## ローカルスコープとは？
> ローカルスコープによりアプリケーション全体で簡単に再利用可能な、一連の共通制約を定義できます。例えば、人気のある(popular)ユーザーを全員取得する必要が、しばしばあるとしましょう。スコープを定義するには、scopeを先頭につけた、Eloquentモデルのメソッドを定義します。

※ [Eloquent：利用の開始 5.4 Laravel](https://readouble.com/laravel/5.4/ja/eloquent.html) ローカルスコープ参照

要約すると、**EloquentモデルのSQL周りをメソッドとして定義する**ことができ、コントローラーから呼び出すことができます。

Railsでもlaravelでもそうですが、僕は直近までコントローラーにSQLを書いていました。
それでも動くのですが、アプリケーションの規模が膨大になってきたり、SQLの一部を変更したい場合に、そのSQLを書いている箇所全て直さなければいけません。
ちょっとそれだとイケてないので、**ローカルスコープ**を使ってSQLを関数に直したいと思います。

## 注意点
ローカルスコープは、**クエリビルダに対しては適用できない**ので注意
例)

```
値が取れる例

    $a = Customer::select(DB::raw('count(detail)')) //この部分の記述が違う
      ->joinContacts()
      ->joinContactCategories()
      ->whereRaw('age >= 18')
      ->whereRaw('age <= 22')
      ->groupBy('age')
      ->get();
```

```
値が取れなかった例

    $a = DB::table('customers') //この部分の記述が違う
      ->select(DB::raw('count(detail)'))
      ->joinContacts()
      ->joinContactCategories()
      ->whereRaw('age >= 18')
      ->whereRaw('age <= 22')
      ->groupBy('age')
      ->get();
```

## 以前のコントローラー
処理内容としては、複数ドロップダウンメニューが設置してあり、その選択した結果に応じて動的にグラフを変えるというものです。
- `$area = $request->area;`等の部分は、ドロップダウンメニューのvalueを$requestとして受け取っています。
- `->join...`の部分は、複数テーブルをjoinしています。
- `->when($area, function... `の部分は、第一引数が正だった場合に where句を使用しています。
つまり、ドロップダウンメニューの値が選択されていればwhere句を使用し、選択されていなければwhere句を使用しないという処理になります。

```HomeController.php
  public function getCharts(Request $request)
  {
    $area = $request->area;
    $rent = $request->rent;
    $country = $request->country;
    $gender = $request->gender;
    $age = $request->age;

    $query = DB::table('residents')
      ->select(DB::raw('count(*) as value, month'))
      ->join('properties as pr', 'pr.id', '=', 'residents.property_id')
      ->join('ages as ag', 'ag.id', '=', 'residents.age_id')
      ->join('countries as co', 'co.id', '=', 'residents.country_id')
      ->join('areas as ar', 'ar.id', '=', 'pr.area_id')
      ->when($area, function ($query) use ($area) {
        return $query->where('area_id', '=', $area);
      })
      ->when($rent, function ($query) use ($rent) {
        return $query->where('rent', '=', $rent);
      })
      ->when($country, function ($query) use ($country) {
        return $query->where('country_id', '=', $country);
      })
      ->when($gender, function ($query) use ($gender) {
        return $query->where('gender', '=', $gender);
      })
      ->when($age, function ($query) use ($age) {
        return $query->where('', '=', $age);
      })
      ->groupBy('month');
      
    return $query->get();
  }
```

## ローカルスコープの使い方
ローカルスコープはモデルに定義します。
scope+メソッド名 という形で記述していきます。
メソッド名は処理の内容が明確になるような命名がいいです。(自分がまだ出来てないですが...)

```モデル.php
public function scope+名前($query, 引数)
    {
      # 処理内容
      return 絞り込んだビルダ(検索条件)
    }
```
### モデルへの記述
residents(入居者)テーブルに対してクエリを投げたかったので、Residentモデルに全て記述しました。
(ベストプラクティスが知りたい...)

```モデル.php
// JOIN周り
  public function scopeJoinProperties($query)
  {
    return $query->join('properties as pr', 'pr.id', '=', 'residents.property_id');
  }

  public function scopeJoinAges($query)
  {
    return $query->join('ages as ag', 'ag.id', '=', 'residents.age_id');
  }

  public function scopeJoinCountries($query)
  {
    return $query->join('countries as co', 'co.id', '=', 'residents.country_id');
  }

  public function scopeJoinAreas($query)
  {
    return $query->join('areas as ar', 'ar.id', '=', 'pr.area_id');
  }


// Where句周り
  public function scopeWhereAreas($query, $area)
  {
    if ($area != '') {
      return $query->where('area_id', '=', $area);
    }
  }

  public function scopeWhereCountries($query, $country)
  {
    if ($country != '') {
      return $query->where('country_id', '=', $country);
    }
  }

  public function scopeWhereAges($query, $age)
  {
    if ($age != '') {
      $query->where('age_id', '=', $age);
    }
  }

  public function scopeWhereGender($query, $gender)
  {
    if ($gender != '') {
      $query->where('gender', '=', $gender);
    }
  }

  public function scopeWhereRent($query, $rent)
  {
    if ($rent != '') {
      $query->where('rent', '=', $rent);
    }
  }

```

### コントローラー側での呼び出し

コントローラー側で呼び出すときは、 **scopeを除外した変数名**で呼び出します。

【例】
`scopeJoinProperties`メソッドであれば、
コントローラーから呼び出す時に `joinproperties()` のような形で呼び出します。

```HomeController.php
  public function getCharts(Request $request)
  {
    $area = $request->area;
    $rent = $request->rent;
    $country = $request->country;
    $gender = $request->gender;
    $age = $request->age;

    $query = Resident::select()
      ->select(DB::raw('count(*) as value, month'))
      ->joinProperties()
      ->joinAges()
      ->joinCountries()
      ->joinAreas()
      ->joinRoomtypes()
      ->whereAreas($area)
      ->whereAges($age)
      ->whereCountries($country)
      ->whereGender($gender)
      ->whereRent($rent)
      ->groupBy('month');

    return $query->get();

  }
```
見た目的にもだいぶスッキリしたように見えます。
まだ荒削りなので、もっと簡略化できる部分を探していきます。

他にも**グローバルスコープ**なるものがあるらしいので、使う機会が来たらまた記事にまとめます。

ここもっとこうした方がいいよ、等のご指摘ありましたらいただけますと幸いです。

## 参考資料
- [【Laravel】毎回同じ検索条件を書くならEloquentのクエリスコープを使う - Qiita](https://qiita.com/youstr/items/eef1caa17abac4879727)
- [Laravelでスコープ - Qiita](https://qiita.com/mikaku/items/09ff1adecb2dfb97269d)