Laravelのモデルのスコープ【個人的なお勉強アウトプット】

## 参考図書
https://www.amazon.co.jp/PHP%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AF%E3%83%BC%E3%82%AF-Laravel%E5%85%A5%E9%96%80-%E6%8E%8C%E7%94%B0%E6%B4%A5%E8%80%B6%E4%B9%83/dp/4798052582

モデルの利点の一つとしてスコープがある。
モデルのスコープとは、モデルの範囲を特定するためのもの。

## モデルの考え方
複雑な検索はwhereでつなぎあわせて行うと非常にわかりづらくなる。
例えば「20歳以上の女性で今月会員登録した山田さん」など。
そこで、モデルに「こういう条件のもの」といったスコープを設定するメソッドを用意しておき、それを利用して細かな条件を設定し、検索をわかりやすく行える。
上記の例だと、「20歳以上」「女性」「今月登録した」といったスコープが用意されていれば、名前が「山田さん」という条件だけ用意すれば済む

スコープにはローカルスコープとグローバルスコープがある。

## ローカルスコープ
モデル内にメソッドを用意しておき、必要に応じてそれらのメソッドを明示的に呼び出して条件を絞り込むもの。
メソッドを呼び出さなければスコープは機能しない。
```
public function scope名前($query, 引数){
...必要な処理...
return 絞り込んだビルダ;
}
```

### 実例：nameをスコープにする
```app/Models/Person.php
public function scopeNameEqual($query, $str){
return $query->where('name', $str);
}
```
第2引数に`$str`という小目を用意し、これを利用し`$query->where('name', $str);`を実行した結果をreturnしている。
これでnameの値が`$str`であるBuilderインスタンスに返されることになる。

#### nameEqualを利用する
```app/Http/Controllers/PersonController.php
public function search(Request $request){
$item = Person::nameEqual($request->input)->first();
$param = ['input' => $request->input, 'item' => $item];
return view('person.find' $param);
}
```
先程のスコープを`nameEqual($request->input)`として呼び出している。スコープに用意したメソッドを呼び出す場合は、メソッド名のscopeは不要。

### 実例：スコープを組み合わせる
```app/Models/Person.php
//ageの引数の値と等しいかもっと大きいかを絞り込む
public function scopeAgeGreaterThan($query, $n){
return $query->where('age', '>=', $n);
}

//ageの引数の値と等しいかもっと小さいかかを絞り込む
public function scopeAgeLessThan($query, $n){
return $query->where('age', '<=', $n);
}
```
この２つを組み合わせることで○○以上○○以下の条件が簡単に設定できる。

```app/Http/Controllers/PersonController.php
public function search(Request $request){
$min = $request->input * 1;
$max = $min + 10;
$item = Person::ageGreatherThan($min)->ageLessThan($max)->first();
$param = ['input' => $request->input, 'item' => $item];
return view('person.find', $param);
}
```
上記のように連続して呼び出すことで絞り込みを行える。

## グローバルスコープ
ローカルスコープは検索条件を設定してBuilderを返すメソッドを用意して、それを呼び出すというものだった。
それに対してグローバルスコープは、処理を用意しておくだけで、そのモデルでのすべてのレコード取得にそのスコープが適用される。
グローバルスコープは、ローカルスコープのようにメソッドを追加すればOKではなく、専用のメソッドが用意位されていて、それを利用して処理を組み込む。

### bootメソッド
処理の組み込みはモデルが作成される際の初期化処理として実行する。bootというモデルの初期化専用のメソッドを用いる。
```
protected static function boot(){
parent::boot();
...初期化処理...
}
```
`::boot();`としているので、クラスに対して行う静的メソッド。なので、モデルインスタンス自身を利用する`$this`は使えない。
どうやってグローバルスコープの処理を用意するのかというとaddGlobalScopeという静的メソッドを使う。
```
static::addGlobalScope(スコープ名, function(Builder $builder){
...絞り込み処理
}
```
addGlocalScopeはグローバルスコープを追加するメソッド。これをオーバーライドすることでグローバルスコープを追加できる。
第一引数にスコープの名前を、第2引数にクロージャを用意している。クロージャはBuilderインスタンスが引数として渡される。Builderを使ってスコープの絞り込み処理を作成する。

### 実例：グローバルスコープを作成
```app/Models/Person.php
use Illuminate\Database\Eloquent\Builder;
protected static function boot(){
parent::boot();
static::addGlobalScope('age', function(Builder $builder){
$builder->where('age', '>', 20);
});
}
```
上記によって、Personモデルの検索はすべて上記の絞り込みが適用される。

### Scopeクラスを作成
クロージャを使ったグローバルスコープは、特定のモデルにグローバルスコープを追加できて便利。だが、複数のモデルや、そのほかのプロジェクトでも利用されるような汎用性の高い処理は「Scopeクラス」として作成しておくと便利。
Scopeクラスは、Illuminate\Database\Eroquent名前空間にあるScopeを実装して定義されるクラス。
```
class クラス名 implements Scope{
public function apply(Builder $builder, Model $model){
...絞り込み処理
}
}
```
Scopeクラスはapplyメソッドを用意する。このメソッドではBuilderとModelがインスタンスとして渡される。これらの引数を用いて処理を行う。モデルから切り離され、引数にModelを渡して処理するため、特定のモデルにとらわれず、汎用的な処理を行うスコープが作成できる。

#### ScopePersonクラスをつくる
Scopeクラスをつくる場所は決まってない。ただ、専用のフォルダを用意してそこにまとめたほうがわかりやすい。
app/Scopesフォルダをつくる。
```app/Scopes/ScopePerson.php
namespace App\Scopes;

use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class ScopePerson implements Scome{
public function apply(Builder $builder, Model $model){
$builder->where('age', '>', 20);
}
}
```
applyメソッドの中で`$builder->where('age', '>', 20);`を実行しているだけ。

#### ScopePersonクラスの利用
```app/Models/Person.php
use App\Scopes\ScopePerson;

protected static function boot(){
parent::boot();
static::addGlobalScope(new ScopePerson);
}
```
これでグローバルスコープが追加される。