全局作用域
所謂「全局作用域」,指的是預置過濾器在注冊該「全局作用域」的模型類的所有查詢中生效,不需要指定任何額外條件。
以 User
模型類為例,我們在系統中可能只想針對已經驗證過郵箱的用戶進行操作,在沒有介紹「作用域」之前,可能你會在應用中到處編寫這樣的代碼:
$users = User::whereNotNull('email_verified_at')->...
通過全局作用域類實現
要實現「全局作用域」,首先需要編寫一個實現 Illuminate\Database\Eloquent\Scope
接口的全局作用域類,這里我們將其命名為 EmailVerifiedAtScope
,並將其放到 app/Scopes
目錄下:
<?php namespace App\Scopes; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Scope; class EmailVerifiedAtScope implements Scope { public function apply(Builder $builder, Model $model) { return $builder->whereNotNull('email_verified_at'); } }
在這個全局作用域類中,只需要實現 apply
方法即可,在該方法中,在查詢構建器上應用過濾器方法並將其返回。
然后,我們需要將這個全局作用域類注冊到 User
模型類上,這樣,在 User
模型類上進行查詢的時候才可以應用相應的過濾條件。這個工作可以通過在 User
模型類中重寫父類的 boot
方法來完成:
protected static function boot() { parent::boot(); static::addGlobalScope(new EmailVerifiedAtScope()); }
注:boot
方法會在模型類實例化的時候調用。你可以在這里進行一些模型類的初始化操作。
通過匿名函數實現
如果你覺得編寫一個「全局作用域」類很麻煩,過濾邏輯又很簡單,還可以在模型類的 boot
方法中通過匿名函數實現全局作用域:
protected static function boot() { parent::boot(); //static::addGlobalScope(new EmailVerifiedAtScope()); static::addGlobalScope('email_verified_at_scope', function (Builder $builder) { return $builder->whereNotNull('email_verified_at'); }); }
實現效果和上面通過全局作用域類完全一樣。
移除全局作用域
在某些特定場景下,我們可能需要移全局作用域,比如在后台用戶管理頁,我們需要將未驗證郵箱的用戶頁顯示出來,這個時候我們可以借助模型類的 withoutGlobalScope
方法來實現,該方法支持多種傳參格式,移除多種全局作用域及其組合:
User::withoutGlobalScope(EmailVerifiedAtScope::class)->get(); # 指定類 User::withoutGlobalScope('email_verified_at_scope')->get(); # 匿名函數 User::withoutGlobalScopes()->get(); # 移除所有全局作用域 User::withoutGlobalScopes([FirstScope::class, SecondScope::class])->get(); # 移除多個類/匿名函數
局部作用域
所謂「局部作用域」,指的是預置過濾器在對應模型類的指定查詢中生效,與「全局作用域」不同,「局部作用域」需要額外指定才能生效,但是相應的,也更加靈活,可以適用於不同場景。
「局部作用域」的實現也比較簡單,在需要應用它的模型類中定義一個過濾器方法即可。該方法需要以 scope
開頭,然后附加該過濾器的名稱,以文章列表頁顯示最流行文章為例(按照瀏覽數逆序),可以在 Post
模型類中編寫一個 scopePopular
方法:
public function scopePopular(Builder $query) { return $query->where('views', '>', '0')->orderBy('views', 'desc'); }
而在文章詳情頁,我們希望展示的是已發布的文章詳情,如果文章沒有發布,返回 404,因此我們再定義一個「局部作用域」方法 scopeActive
:
public function scopeActive(Builder $query) { return $query->where('status', Post::ACTIVED); }
在模型類上調用「局部作用域」過濾器方法只需調用 scope
之后的過濾器名稱即可,Eloquent 底層會通過魔術方法自動調用對應完整方法:
$post = Post::active()->find(100); $post = Post::active()->popular()->get();
動態作用域
此外,Eloquent 模型類還支持「動態作用域」,所謂動態作用域指的是在查詢過程中動態設置預置過濾器的查詢條件,動態作用域和局部作用域類似,過濾器方法名同樣以 scope
開頭,只不過可以通過額外參數指定查詢條件,比如我要在文章中查詢指定類型的文章,可以通過在 Post
模型類中定義如下方法:
public function scopeOfType(Builder $query, $type) { return $query->where('type', $type); }
這樣,在查詢指定類型的文章時,就可以這么實現:
$posts = Post::active()->ofType(Post::Article)->get();