九保すこひ@フリーランスエンジニア|累計300万PVのブログ運営中
さてさて、この間リリースされたLaravel
の新バージョン8.x
については私の中で一旦落ち着きましたので、今回は新機能ではなく、元々ある便利な機能に的を絞ってご紹介していきたいと思います。
ズバリ、その紹介したい機能は・・・・・・
スコープ
です。
ここで言うスコープというのは、JavaScript
のスコープとは少し違っていて、主にDBテーブルの絞り込みをするwhere句
に関連する機能のことです。
そして、一言でスコープといってもLaravel
には以下2つのスコープがあって、どちらを使うべきかは状況によって変わってきます。
- グローバル・スコープ
- ローカル・スコープ
そこで❗
今回は、この2つのスコープをご紹介していきたいと思います。
ぜひ皆さんのお役に立てましたら嬉しいです😊✨
「5%だと思ったら、しっかり
ストロングの9%でヘロヘロです😂」
開発環境: Laravel 8.x(スコープは過去バージョンでも使えます)
目次
「うっかり」を減らせるグローバル・スコープ
グローバル・スコープとは
グローバル・スコープは、簡単に言うと、「いつでも絞り込みが有効になる」機能です。
実際の例を見てみましょう。
いま、ユーザーが投稿した記事を保存するposts
というテーブルがあるとします。
もちろん誰でも投稿できるので、いろんなuser_id
がデータとして保存されています。
・・・ということは、コードを書くときにひとつ気をつけないといけないことが出てきます。
そうです。「ログインしているユーザーのIDで絞り込みをしないと、他人の投稿まで変更や削除ができてしまう」という点です。
そのため、通常だと以下のようにwhere()
を使って絞り込みをすることになると思います。
$user_id = auth()->id(); $posts = Post::where('user_id', $user_id)->get(); // 👈 本人のデータだけ取得
もちろんこの形でも間違いではありませんが、これに似たコードを何度も何度も書いていると、「うっかり」が発生し、where( ... )
が抜けてしまうことも考えられます。
そんなとき「グローバル・スコープ」の登場です❗
つまり、グローバル・スコープを設定しておくと、書いてなくても必ず user_id の絞り込みを有効にすることができます。
$posts = Post::get(); // 👈 グローバル・スコープがあれば、毎回 user_id で絞り込みが有効になります
これは、get()
だけでなく、first()
などにも有効になります。
グローバル・スコープを設定する方法
では、実際にグローバル・スコープを設定する方法をご紹介しましょう。
app/Models/Post.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; class Post extends Model { protected static function booted() { static::addGlobalScope('user_id', function (Builder $builder) { $user_id = auth()->id(); $builder->where('user_id', $user_id); }); } }
※ 設定はモデルごとに行います。皆さんはお好きなモデルでやってみてください。
まず、addGlobalScope
のすぐ横にあるuser_id
は名前で、これは好きな文字列でOKです。(ただ、グローバル・スコープを解除するときにこの名前を使うので覚えやすいものがいいでしょう😊)
そして、function() { ... }
内がグローバル・スコープとしていつでも実行されるwhere句
になります。
では、設定前と後で、以下のコードがどう違ってくるか見てみましょう。
$posts = Post::get(); dd($posts->toArray());
(設定する前:全ての user_id がデータとして取得されます)
(設定した後:該当するuser_id
だけが取得されます)
こんなふうにして「うっかり」を減らすことができるわけですね。
グローバルスコープを解除する方法
とはいえ、状況によっては、グローバル・スコープを解除したい場面も出てくると思います。
※ 例えば、管理者がposts
の全データを検索して変更や削除をする場合です。
そんな場合は次のようにすることで簡単にグローバル・スコープを解除することができます。
$posts = Post::withoutGlobalScope('user_id')->get();
user_id
というのは先ほど設定した名前になります。
同じグローバル・スコープを何度も設定するのが面倒な場合
実はグローバル・スコープはもうひとつ設定方法があって、それが独自のクラスをつくる方法です。
この方法では、何度も同じグローバル・スコープをモデルに書かなくてもよくなり、省コード化できます。
では、実際に使い方を見ていきましょう。
以下のファイルを作成してください。
app/Scopes/UserIdScope.php
<?php namespace App\Scopes; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Scope; class UserIdScope implements Scope { public function apply(Builder $builder, Model $model) { $user_id = auth()->id(); $builder->where('user_id', $user_id); } }
※残念ながら、artisan
コマンドはありません😭
そして、このクラスをモデルに設定します。
<?php namespace App\Models; use App\Scopes\UserIdScope; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; class Post extends Model { protected static function booted() { static::addGlobalScope(new UserIdScope); } }
どうでしょう。さっきと比べると、たった1行でグローバル・スコープを有効にすることができるようになりました。
そして、Post
だけでなく、user_id
で絞り込みをしたいモデルにも使い回しができるのでとても便利です👍
なお、解除する場合は次のようになります。
Post::withoutGlobalScope(UserIdScope::class)->get();
ちなみに:where句以外も使えます
わかりやすくするためにwhere句
のみで説明しましたが、orderBy()
なども使えます。
protected static function booted() { static::addGlobalScope('user_id', function (Builder $builder) { $builder->orderBy('user_id', 'desc'); }); }
「ラク」できるローカル・スコープ
ローカル・スコープとは
「ローカル・スコープ」はグローバル・スコープと違って、いつでも自動で有効になるものではありませんが、「必要なときに臨機応変で」ラクができる機能です。
こちらも実際の例を見てみましょう。
例えば、users
テーブルの中から以下の条件に合うデータを取得する場合です。
- 指定した月(例えば今月、先月など)にユーザー登録した
- メール認証を済ませている(
email_verified_at
がnull
ではないもの)
こういった場合、通常は以下のようにしてコードを書くと思います。
$year = 2020; $month = 9; $start_dt = Carbon::createFromDate($year, $month)->firstOfMonth(); $end_dt = $start_dt->copy()->endOfMonth(); $users = User::whereBetween('created_at', [$start_dt, $end_dt]) ->whereNotNull('email_verified_at') ->get();
これももちろん間違いではありません。
ただ、いろんな場所で何度もこのコードが必要になるとしたらどうでしょう。
「うーん、毎回このコード書くのめんどくさい・・・😭」
とはならないでしょうか。
そんなときにローカル・スコープの登場です❗
簡単にいうと、以下のように独自のwhere句
を作ってたった1行だけでOKになります。
$users = User::whereCreated($year, $month)->get(); // 👈 さっきと同じ条件にできる
ローカル・スコープを設定する方法
では、実際にローカル・スコープをつくってみましょう。
グローバル・スコープと同じく各モデル内に設定します。
app/Models/User.php
<?php namespace App\Models; use Carbon\Carbon; // 👈 追加しました // 省略 class User extends Authenticatable { // 省略 // スコープ public function scopeWhereCreated($query, $year, $month) { $start_dt = Carbon::createFromDate($year, $month)->firstOfMonth(); $end_dt = $start_dt->copy()->endOfMonth(); $query->whereBetween('created_at', [$start_dt, $end_dt]) ->whereNotNull('email_verified_at'); }
上のコードを見ていただくとわかるとおり、「scope」+ 「xxxxx」というメソッドをつくると、「xxxxx()」がローカル・スコープになります。
つまり、今回で言うとwhereCreated()
ですね。
なお、変数が不要な場合は、$query
だけ残しておけばOKです。
例えば、今月登録したユーザーだけを取得するスコープはこちらです。
public function scopeWhereCreatedThisMonth($query) { $start_dt = today()->firstOfMonth(); $end_dt = $start_dt->copy()->endOfMonth(); $query->whereBetween('created_at', [$start_dt, $end_dt]) ->whereNotNull('email_verified_at'); }
ちなみに:where句以外も使えます
グローバル・スコープと同じですが以下のようにすると、orderBy()
など別のメソッドで条件指定することもできます。
public function scopeOrderByCreated($query, $direction = 'asc') { $query->orderBy('created_at', $direction); }
使い方はこうなります。
$users = User::orderByCreated('desc')->get();
おわりに
ということで、今回はLaravel
の便利な機能「スコープ」についてご紹介しました。
スコープを使うことで、何度も同じコードを書かなくてよくなりますし、さらに、もし変更があったとしてもスコープの中身だけ書き換えるだけで全ての場所に適用できます。
ということで、省コードになりますので、
- 開発速度が早くなる
- コードが少ない = バグも少ない
となって、我々にとってはメリットが大きいといっていいんじゃないでしょうか。(もちろん使いどころの判断が重要ですけどね👍)
ぜひみなさんもスコープを活用してコードを書いてみてくださいね。
ではでは〜❗
「リアルでもグローバルな視野
がほしいです😂」