您好,登錄后才能下訂單哦!
今天小編給大家分享一下如何實現一個Laravel查詢過濾器的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
在撰寫本文時,我在 PHP 8.1 和 MySQL 8 上使用 Laravel 9。我相信技術棧不是一個大問題,這里我們主要關注構建一個查詢過濾器系統。在本文中,我將演示為 users 表構建過濾器。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* 運行遷移
*
* @return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->string('gender', 10)->nullable()->index();
$table->boolean('is_active')->default(true)->index();
$table->boolean('is_admin')->default(false)->index();
$table->timestamp('birthday')->nullable();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
/**
* 回退遷移
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
}
此外,我還使用 Laravel Telescope 輕松監控查詢。
在學習使用 Laravel 的第一天,我經常直接在控制器上調用過濾器。簡單,沒有魔法,容易理解,但是這種方式有問題:
控制器中放置的大量邏輯導致控制器膨脹
不能重復使用
許多相同的工作重復
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function __invoke(Request $request)
{
// /users?name=ryder&email=hartman&gender=male&is_active=1&is_admin=0&birthday=2014-11-30
$query = User::query();
if ($request->has('name')) {
$query->where('name', 'like', "%{$request->input('name')}%");
}
if ($request->has('email')) {
$query->where('email', 'like', "%{$request->input('email')}%");
}
if ($request->has('gender')) {
$query->where('gender', $request->input('gender'));
}
if ($request->has('is_active')) {
$query->where('is_active', $request->input('is_active') ? 1 : 0);
}
if ($request->has('is_admin')) {
$query->where('is_admin', $request->input('is_admin') ? 1 : 0);
}
if ($request->has('birthday')) {
$query->whereDate('birthday', $request->input('birthday'));
}
return $query->paginate();
// select * from `users` where `name` like '%ryder%' and `email` like '%hartman%' and `gender` = 'male' and `is_active` = 1 and `is_admin` = 0 and date(`birthday`) = '2014-11-30' limit 15 offset 0
}
}
為了能夠在過濾期間隱藏邏輯,讓我們嘗試使用 Laravel 的 Local Scope。將查詢轉換為 User 模型中的函數范圍:
// User.php
public function scopeName(Builder $query): Builder
{
if (request()->has('name')) {
$query->where('name', 'like', "%" . request()->input('name') . "%");
}
return $query;
}
public function scopeEmail(Builder $query): Builder
{
if (request()->has('email')) {
$query->where('email', 'like', "%" . request()->input('email') . "%");
}
return $query;
}
public function scopeGender(Builder $query): Builder
{
if (request()->has('gender')) {
$query->where('gender', request()->input('gender'));
}
return $query;
}
public function scopeIsActive(Builder $query): Builder
{
if (request()->has('is_active')) {
$query->where('is_active', request()->input('is_active') ? 1 : 0);
}
return $query;
}
public function scopeIsAdmin(Builder $query): Builder
{
if (request()->has('is_admin')) {
$query->where('is_admin', request()->input('is_admin') ? 1 : 0);
}
return $query;
}
public function scopeBirthday(Builder $query): Builder
{
if (request()->has('birthday')) {
$query->where('birthday', request()->input('birthday'));
}
return $query;
}
// UserController.php
public function __invoke(Request $request)
{
// /users?name=john&email=desmond&gender=female&is_active=1&is_admin=0&birthday=2015-04-11
$query = User::query()
->name()
->email()
->gender()
->isActive()
->isAdmin()
->birthday();
return $query->paginate();
// select * from `users` where `name` like '%john%' and `email` like '%desmond%' and `gender` = 'female' and `is_active` = 1 and `is_admin` = 0 and `birthday` = '2015-04-11' limit 15 offset 0
}
通過這種設置,我們將大部分數據庫操作移到了模型類中,但是代碼重復非常多。示例 2 的名稱和電子郵件范圍過濾器相同,性別生日和 is_active/is_admin 組相同。我們將對類似的查詢功能進行分組。
// User.php
public function scopeRelativeFilter(Builder $query, $inputName): Builder
{
if (request()->has($inputName)) {
$query->where($inputName, 'like', "%" . request()->input($inputName) . "%");
}
return $query;
}
public function scopeExactFilter(Builder $query, $inputName): Builder
{
if (request()->has($inputName)) {
$query->where($inputName, request()->input($inputName));
}
return $query;
}
public function scopeBooleanFilter(Builder $query, $inputName): Builder
{
if (request()->has($inputName)) {
$query->where($inputName, request()->input($inputName) ? 1 : 0);
}
return $query;
}
// UserController.php
public function __invoke(Request $request)
{
// /users?name=john&email=desmond&gender=female&is_active=1&is_admin=0&birthday=2015-04-11
$query = User::query()
->relativeFilter('name')
->relativeFilter('email')
->exactFilter('gender')
->booleanFilter('is_active')
->booleanFilter('is_admin')
->exactFilter('birthday');
return $query->paginate();
// select * from `users` where `name` like '%john%' and `email` like '%desmond%' and `gender` = 'female' and `is_active` = 1 and `is_admin` = 0 and `birthday` = '2015-04-11' limit 15 offset 0
}
至此,我們已經對大部分重復項進行了分組。但是,刪除 if 語句或將這些過濾器擴展到另一個模型有點困難。我們正在尋找一種方法來徹底解決這個問題。
管道設計模式是一種設計模式,它提供了逐步構建和執行一系列操作的能力。 Laravel 有內置的 Pipeline 讓我們可以很容易地在實際中應用這種設計模式,但由于某種原因,它沒有在官方文檔中列出。 Laravel 本身也將 Pipeline 應用于請求和響應之間的中間件。最基本的,要在 Laravel 中使用 Pipeline,我們可以這樣使用
app(\Illuminate\Pipeline\Pipeline::class)
->send($intialData)
->through($pipes)
->thenReturn(); // data with pipes applied
對于我們的問題,可以將初始查詢 User:query() 傳遞給 pipeline,通過過濾器步驟,并返回應用過濾器的查詢構建器。
// UserController
public function __invoke(Request $request)
{
// /users?name=john&email=desmond&gender=female&is_active=1&is_admin=0&birthday=2015-04-11
$query = app(Pipeline::class)
->send(User::query())
->through([
// filters
])
->thenReturn();
return $query->paginate();
現在我們需要構建管道過濾器:
// File: app/Models/Pipes/RelativeFilter.php
<?php
namespace App\Models\Pipes;
use Illuminate\Database\Eloquent\Builder;
class RelativeFilter
{
public function __construct(protected string $inputName)
{
}
public function handle(Builder $query, \Closure $next)
{
if (request()->has($this->inputName)) {
$query->where($this->inputName, 'like', "%" . request()->input($this->inputName) . "%");
}
return $next($query);
}
}
// File: app/Models/Pipes/ExactFilter.php
<?php
namespace App\Models\Pipes;
use Illuminate\Database\Eloquent\Builder;
class ExactFilter
{
public function __construct(protected string $inputName)
{
}
public function handle(Builder $query, \Closure $next)
{
if (request()->has($this->inputName)) {
$query->where($this->inputName, request()->input($this->inputName));
}
return $next($query);
}
}
//File: app/Models/Pipes/BooleanFilter.php
<?php
namespace App\Models\Pipes;
use Illuminate\Database\Eloquent\Builder;
class BooleanFilter
{
public function __construct(protected string $inputName)
{
}
public function handle(Builder $query, \Closure $next)
{
if (request()->has($this->inputName)) {
$query->where($this->inputName, request()->input($this->inputName) ? 1 : 0);
}
return $next($query);
}
}
// UserController
public function __invoke(Request $request)
{
// /users?name=john&email=desmond&gender=female&is_active=1&is_admin=0&birthday=2015-04-11
$query = app(Pipeline::class)
->send(User::query())
->through([
new \App\Models\Pipes\RelativeFilter('name'),
new \App\Models\Pipes\RelativeFilter('email'),
new \App\Models\Pipes\ExactFilter('gender'),
new \App\Models\Pipes\BooleanFilter('is_active'),
new \App\Models\Pipes\BooleanFilter('is_admin'),
new \App\Models\Pipes\ExactFilter('birthday'),
])
->thenReturn();
return $query->paginate();
// select * from `users` where `name` like '%john%' and `email` like '%desmond%' and `gender` = 'female' and `is_active` = 1 and `is_admin` = 0 and `birthday` = '2015-04-11' limit 15 offset 0
}
通過將每個查詢邏輯移動到一個單獨的類,我們解鎖了使用 OOP 的定制可能性,包括多態、繼承、封裝、抽象。比如你在 pipeline 的 handle 函數中看到,只有 if 語句中的邏輯不同,我會通過創建抽象類 BaseFilter 的方式將其分離抽象出來
//File: app/Models/Pipes/BaseFilter.php
<?php
namespace App\Models\Pipes;
use Illuminate\Database\Eloquent\Builder;
abstract class BaseFilter
{
public function __construct(protected string $inputName)
{
}
public function handle(Builder $query, \Closure $next)
{
if (request()->has($this->inputName)) {
$query = $this->apply($query);
}
return $next($query);
}
abstract protected function apply(Builder $query): Builder;
}
// BooleanFilter
class BooleanFilter extends BaseFilter
{
protected function apply(Builder $query): Builder
{
return $query->where($this->inputName, request()->input($this->inputName) ? 1 : 0);
}
}
// ExactFilter
class ExactFilter extends BaseFilter
{
protected function apply(Builder $query): Builder
{
return $query->where($this->inputName, request()->input($this->inputName));
}
}
// RelativeFilter
class RelativeFilter extends BaseFilter
{
protected function apply(Builder $query): Builder
{
return $query->where($this->inputName, 'like', "%" . request()->input($this->inputName) . "%");
}
}
現在我們的過濾器直觀且高度可重用,易于實現甚至擴展,只需創建一個管道,擴展 BaseFilter 并聲明函數 apply 即可應用到 Pipeline.中。
此時,我們將嘗試在控制器上隱藏 Pipeline,通過在 Model 中創建一個調用 Pipeline 的作用域來使我們的代碼更簡潔。
// User.php
public function scopeFilter(Builder $query)
{
$criteria = $this->filterCriteria();
return app(\Illuminate\Pipeline\Pipeline::class)
->send($query)
->through($criteria)
->thenReturn();
}
public function filterCriteria(): array
{
return [
new \App\Models\Pipes\RelativeFilter('name'),
new \App\Models\Pipes\RelativeFilter('email'),
new \App\Models\Pipes\ExactFilter('gender'),
new \App\Models\Pipes\BooleanFilter('is_active'),
new \App\Models\Pipes\BooleanFilter('is_admin'),
new \App\Models\Pipes\ExactFilter('birthday'),
];
}
// UserController.php
public function __invoke(Request $request)
{
// /users?name=john&email=desmond&gender=female&is_active=1&is_admin=0&birthday=2015-04-11
return User::query()
->filter()
->paginate()
->appends($request->query()); // 將所有當前查詢附加到分頁鏈接中
// select * from `users` where `name` like '%john%' and `email` like '%desmond%' and `gender` = 'female' and `is_active` = 1 and `is_admin` = 0 and `birthday` = '2015-04-11' limit 15 offset 0
}
用戶現在可以從任何地方調用過濾器。但是其他模型也想實現過濾,我們將創建一個包含范圍的 Trait,并在模型內部聲明參與過濾過程的 Pipeline。
// User.php
use App\Models\Concerns\Filterable;
class User extends Authenticatable {
use Filterable;
protected function getFilters()
{
return [
new \App\Models\Pipes\RelativeFilter('name'),
new \App\Models\Pipes\RelativeFilter('email'),
new \App\Models\Pipes\ExactFilter('gender'),
new \App\Models\Pipes\BooleanFilter('is_active'),
new \App\Models\Pipes\BooleanFilter('is_admin'),
new \App\Models\Pipes\ExactFilter('birthday'),
];
}
// 其余代碼
// File: app/Models/Concerns/Filterable.php
namespace App\Models\Concerns;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Pipeline\Pipeline;
trait Filterable
{
public function scopeFilter(Builder $query)
{
$criteria = $this->filterCriteria();
return app(Pipeline::class)
->send($query)
->through($criteria)
->thenReturn();
}
public function filterCriteria(): array
{
if (method_exists($this, 'getFilters')) {
return $this->getFilters();
}
return [];
}
}
以上就是“如何實現一個Laravel查詢過濾器”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。