13
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Person Link newAdvent Calendar 2020

Day 25

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

Last updated at Posted at 2020-12-24

ローカルスコープとは?

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

Eloquent:利用の開始 5.4 Laravel ローカルスコープ参照

要約すると、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();

  }

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

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

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

参考資料

13
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?