Penapis Pertanyaan... Soalan lazim semasa membangunkan sistem. Tetapi apabila mula menulis kod, terdapat banyak soalan biasa yang timbul untuk setiap pembangun: "Di manakah saya harus meletakkan logik pertanyaan ini? Bagaimana saya harus mengurusnya untuk kemudahan penggunaan?". Secara jujur, untuk setiap projek yang saya bangunkan, saya menulis dalam gaya yang berbeza berdasarkan pengalaman saya dengan projek yang saya buat sebelum ini. Dan setiap kali saya memulakan projek baharu, kali ini saya bertanya kepada diri sendiri soalan yang sama, bagaimana saya boleh mengatur penapis pertanyaan! Artikel ini boleh dianggap sebagai pembangunan langkah demi langkah bagi sistem penapisan pertanyaan, dengan isu yang sepadan.
Pada masa menulis artikel ini, saya menggunakan Laravel 9 pada PHP 8.1 dan MySQL 8. Saya percaya susunan teknologi bukanlah satu isu besar, di sini kami memberi tumpuan terutamanya kepada membina sistem penapis pertanyaan. Dalam artikel ini, saya akan menunjukkan membina penapis untuk jadual pengguna.
<?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'); } }
Selain itu, saya menggunakan Teleskop Laravel untuk memantau pertanyaan dengan mudah.
Pada hari pertama saya belajar menggunakan Laravel, saya sering memanggil penapis terus pada pengawal. Mudah, tiada sihir, mudah difahami, tetapi terdapat masalah dengan pendekatan ini:
<?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 } }
Untuk dapat menyembunyikan logik semasa penapisan, mari cuba menggunakan Skop Tempatan Laravel. Tukar pertanyaan kepada skop fungsi dalam model Pengguna:
// 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 }
Dengan persediaan ini, kami mengalihkan kebanyakan operasi pangkalan data ke dalam kelas model, tetapi terdapat banyak pertindihan kod. Contoh 2 mempunyai nama yang sama dan penapis julat e-mel, hari lahir jantina yang sama dan kumpulan is_active/is_admin. Kami akan mengumpulkan fungsi pertanyaan yang serupa.
// 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 }
Pada ketika ini kami telah mengumpulkan kebanyakan pendua. Walau bagaimanapun, agak sukar untuk mengalih keluar pernyataan if atau melanjutkan penapis ini kepada model lain. Kami sedang mencari jalan untuk menyelesaikan masalah ini sekali dan untuk semua.
Corak Reka Bentuk Talian Paip ialah corak reka bentuk yang menyediakan keupayaan untuk membina dan melaksanakan urutan langkah operasi demi langkah. Laravel mempunyai Talian Paip terbina dalam yang membolehkan kami menggunakan corak reka bentuk ini dengan mudah dalam amalan, tetapi atas sebab tertentu ia tidak disenaraikan dalam dokumentasi rasmi. Laravel sendiri juga menggunakan Pipelines sebagai middleware antara permintaan dan respons. Pada asasnya, untuk menggunakan Talian Paip dalam Laravel, kita boleh menggunakan
app(\Illuminate\Pipeline\Pipeline::class) ->send($intialData) ->through($pipes) ->thenReturn(); // data with pipes applied
Untuk masalah kita, kita boleh menghantar pertanyaan awal User:query() ke saluran paip, melalui langkah penapis dan kembali kepada aplikasi Pembina pertanyaan untuk penapis.
// 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();
Sekarang kita perlu membina penapis saluran paip:
// 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 }
Dengan mengalihkan setiap logik pertanyaan ke dalam kelas yang berasingan, kami membuka kunci kemungkinan penyesuaian menggunakan OOP, termasuk Polimorfisme, warisan, enkapsulasi , abstraksi. Sebagai contoh, anda boleh melihat dalam fungsi pemegang saluran paip bahawa hanya logik dalam pernyataan if yang berbeza saya akan memisahkan dan mengabstrakkannya dengan mencipta kelas abstrak 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) . "%"); } }
Sekarang penapis kami adalah intuitif. dan sangat Boleh Digunakan Semula, mudah untuk dilaksanakan dan juga dilanjutkan, hanya buat saluran paip, lanjutkan BaseFilter dan isytiharkan fungsi digunakan untuk digunakan pada Saluran Paip.
Pada ketika ini, kami akan cuba menyembunyikan Pipeline pada pengawal dengan mencipta skop dalam Model yang memanggil Pipeline ringkas.
// 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 }
Pengguna kini boleh menggunakan penapis dari mana-mana sahaja. Tetapi model lain juga ingin melaksanakan penapisan, kami akan mencipta Trait yang mengandungi skop, dan mengisytiharkan Saluran Paip yang mengambil bahagian dalam proses penapisan di dalam model.
// 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 []; } }
Kami telah menyelesaikan masalah divide and conquer, setiap fail, setiap kelas, setiap fungsi kini mempunyai tanggungjawab yang jelas. Kod ini juga bersih, intuitif dan lebih mudah untuk digunakan semula, bukan! Saya meletakkan kod keseluruhan proses Demo siaran ini di sini.
Di atas adalah sebahagian daripada cara saya membina sistem penapis pertanyaan lanjutan, dan juga memperkenalkan anda kepada beberapa kaedah pengaturcaraan Laravel, seperti Skop Tempatan dan terutamanya corak reka bentuk saluran paip. Untuk menggunakan persediaan ini dengan cepat dan mudah pada projek baharu, anda boleh menggunakan pakej Koleksi Pertanyaan Saluran Paip, yang merangkumi satu set saluran paip pra-bina, menjadikannya mudah dipasang dan digunakan. Saya harap anda semua akan menyokong saya!
Alamat asal: https://baro.rezonia.com/blog/building-a-sexy-query-filter
Alamat terjemahan: https://learnku.com/ laravel/t/68762
Untuk lebih banyak pengetahuan berkaitan pengaturcaraan, sila lawati: Video Pengaturcaraan! !
Atas ialah kandungan terperinci Ajar anda langkah demi langkah cara melaksanakan penapis pertanyaan Laravel. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!