クエリ フィルター... システム開発時によくある問題。しかし、コードを書き始めると、「このクエリ ロジックをどこに配置すればよいでしょうか? 使いやすくするためにはどのように管理すればよいですか?」という、すべての開発者にとってよくある質問が数多く発生します。正直に言うと、私は開発するプロジェクトごとに、以前に作成したプロジェクトの経験に基づいて、異なるスタイルで執筆しています。そして、新しいプロジェクトを開始するたびに、今度は同じ質問を自分自身に問いかけます。クエリ フィルターをどのように配置すればよいでしょうか。この記事は、対応する問題を伴うクエリ フィルタリング システムの段階的な開発と考えることができます。
この記事を書いている時点では、私は 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 } }
// 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 }
// 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 ステートメントを削除したり、これらのフィルターを別のモデルに拡張したりするのは少し困難です。私たちはこの問題を完全に解決する方法を探しています。
パイプライン設計パターンの使用app(\Illuminate\Pipeline\Pipeline::class) ->send($intialData) ->through($pipes) ->thenReturn(); // data with pipes applied
を使用できます。この問題では、最初のクエリ User: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([
// 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 を使用したカスタマイズの可能性が解放されます。たとえば、パイプラインのハンドル関数では、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 に適用するだけです。
ローカル スコープとパイプラインの結合// 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 }
// 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クエリフィルターを実装する方法を段階的に説明します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。