站長資訊網
        最全最豐富的資訊網站

        手把手教你實現一個 Laravel 查詢過濾器

        手把手教你實現一個 Laravel 查詢過濾器

        查詢過濾器… 開發系統時常見的問題。但是當開始編寫代碼時,每個開發人員都會出現許多熟悉的問題:「我應該把這個查詢邏輯放在哪里?我應該如何管理它以方便使用?」。老實說,對于我開發的每個項目,我都會根據以前創建的項目的經驗以不同的風格寫作。而每次我開始一個新項目,這一次我都會問自己同樣的問題,我如何安排查詢過濾器!本文可以認為是一個查詢過濾系統的逐步開發,有相應的問題。

        上下文

        在撰寫本文時,我在 PHP 8.1 和 MySQL 8 上使用 Laravel 9。我相信技術棧不是一個大問題,這里我們主要關注構建一個查詢過濾器系統。在本文中,我將演示為 users 表構建過濾器。

        <?php  use IlluminateDatabaseMigrationsMigration; use IlluminateDatabaseSchemaBlueprint; use IlluminateSupportFacadesSchema;  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 AppHttpControllers;  use AppModelsUser; use IlluminateHttpRequest;  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     } }
        登錄后復制

        使用 Local Scope

        為了能夠在過濾期間隱藏邏輯,讓我們嘗試使用 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(IlluminatePipelinePipeline::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 AppModelsPipes;  use IlluminateDatabaseEloquentBuilder;  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 AppModelsPipes;  use IlluminateDatabaseEloquentBuilder;  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 AppModelsPipes;  use IlluminateDatabaseEloquentBuilder;  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 AppModelsPipesRelativeFilter('name'),             new AppModelsPipesRelativeFilter('email'),             new AppModelsPipesExactFilter('gender'),             new AppModelsPipesBooleanFilter('is_active'),             new AppModelsPipesBooleanFilter('is_admin'),             new AppModelsPipesExactFilter('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 AppModelsPipes;  use IlluminateDatabaseEloquentBuilder;  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.中。

        將 Local Scope 與 Pipeline 相結合

        此時,我們將嘗試在控制器上隱藏 Pipeline,通過在 Model 中創建一個調用 Pipeline 的作用域來使我們的代碼更簡潔。

        // User.php public function scopeFilter(Builder $query) {     $criteria = $this->filterCriteria();     return app(IlluminatePipelinePipeline::class)         ->send($query)         ->through($criteria)         ->thenReturn(); }  public function filterCriteria(): array {     return [         new AppModelsPipesRelativeFilter('name'),         new AppModelsPipesRelativeFilter('email'),         new AppModelsPipesExactFilter('gender'),         new AppModelsPipesBooleanFilter('is_active'),         new AppModelsPipesBooleanFilter('is_admin'),         new AppModelsPipesExactFilter('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 AppModelsConcernsFilterable;  class User extends Authenticatable {         use Filterable;          protected function getFilters()         {             return [                 new AppModelsPipesRelativeFilter('name'),                 new AppModelsPipesRelativeFilter('email'),                 new AppModelsPipesExactFilter('gender'),                 new AppModelsPipesBooleanFilter('is_active'),                 new AppModelsPipesBooleanFilter('is_admin'),                 new AppModelsPipesExactFilter('birthday'),             ];         }          // 其余代碼  // File: app/Models/Concerns/Filterable.php  namespace AppModelsConcerns;  use IlluminateDatabaseEloquentBuilder; use IlluminatePipelinePipeline;  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 [];     } }
        登錄后復制

        我們已經解決了分而治之的問題,每個文件,每個類,每個函數現在都有明確的職責。代碼也干凈、直觀且更易于重用,不是嗎!我把這個帖子 Demo 整個流程的代碼都放在這里了。

        結語

        以上是我構建高級查詢過濾器系統的一部分,同時向你介紹了一些 Laravel 編程方法,例如 Local Scope 尤其是 Pipeline 設計模式。要快速輕松地將此設置應用于新項目,你可以使用包 Pipeline Query Collection,其中包括一組預構建的管道,使其易于安裝和使用。希望大家多多支持!

        原文地址:https://baro.rezonia.com/blog/building-a-sexy-query-filter

        譯文地址:https://learnku.com/laravel/t/68762

        贊(0)
        分享到: 更多 (0)
        網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
        主站蜘蛛池模板: 久久国产精品无码HDAV| 中文字幕亚洲综合精品一区| www.久久精品| 亚洲国产精品成人精品无码区在线 | 一区二区三区精品高清视频免费在线播放 | 欧美精品一二区| 狠狠色伊人久久精品综合网| 精品福利资源在线| 69国产成人综合久久精品| 久久精品中文闷骚内射| 亚洲精品~无码抽插| 亚洲国产综合精品中文字幕 | 精品亚洲A∨无码一区二区三区| 中文成人无字幕乱码精品区| 久久久久99精品成人片牛牛影视| 国产三级精品三级| 国内精品久久久久久久影视麻豆| 免费91麻豆精品国产自产在线观看| 国产精品欧美日韩| 国语自产拍精品香蕉在线播放| 国产精品爽黄69天堂a| 99久久这里只有精品| 国产精品1区2区3区在线播放| 99久久精品午夜一区二区| 成人精品综合免费视频| 国产成人精品免费视频大全麻豆 | 久久精品国产色蜜蜜麻豆| 精品国产不卡一区二区三区| 国内精品久久久久久久久电影网| 国产乱子伦精品无码专区| 国产一区精品| 午夜精品久久影院蜜桃| 亚洲国产精品无码av| 色婷婷在线精品国自产拍| 精品一区二区三区免费毛片爱| 国产伦精品一区二区三区| www国产精品| 国产99久久久国产精品~~牛| 精品国产污污免费网站入口| 亚洲精品网站在线观看不卡无广告 | 欧美日韩精品乱国产538|