QueryFilter: konsep model penyaringan

Saya ingin menarik perhatian Anda konsep mengatur pemfilteran dengan permintaan URL. Sebagai contoh, saya akan menggunakan bahasa PHP dan kerangka Laravel.

Konsep


Idenya adalah untuk membuat kelas QueryFilter universal untuk bekerja dengan filter.

GET /posts?title=source&status=active 

Dengan menggunakan contoh ini, kami akan memfilter posting (model Posting) dengan kriteria berikut:

  • Kehadiran kata "sumber" di bidang judul ;
  • Nilai "terbitkan" di bidang status ;

Contoh aplikasi


Model Pos

 <?php namespace App; use Illuminate\Database\Eloquent\Model; class Post extends Model { /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'id', 'title', 'slug', 'status', 'type', 'published_at', 'updated_at', ]; } 

Tambahkan rute:

 Route::get('/posts', 'PostController@index'); 

Buat file Resource \ Post untuk output dalam format JSON :

 namespace App\Http\Resources; use Illuminate\Http\Resources\Json\JsonResource; class Post extends JsonResource { /** * Transform the resource into an array. * * @param \Illuminate\Http\Request $request * @return array */ public function toArray($request) { return [ 'id' => $this->ID, 'title' => $this->post_title, 'slug' => $this->post_name, 'status' => $this->post_status, 'type' => $this->post_type, 'published_at' => $this->post_date, 'updated_at' => $this->post_modified, ]; } } 

Dan controller itu sendiri dengan satu aksi tunggal:

 namespace App\Http\Controllers; use App\Http\Resources\Post as PostResource; use App\Post; class PostController extends Controller { /** * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection */ public function index() { $posts = Post::limit(10)->get(); return PostResource::collection($posts); } } 

Pemfilteran standar diatur oleh kode berikut:

 /** * @param Request $request * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection */ public function index(Request $request) { $query = Post::limit(10); if ($request->filled('status')) { $query->where('post_status', $request->get('status')); } if ($request->filled('title')) { $title = $request->get('title'); $query->where('post_title', 'like', "%$title%"); } $posts = $query->get(); return PostResource::collection($posts); } 

Dengan pendekatan ini, kita dihadapkan dengan pertumbuhan controller, yang tidak diinginkan.

Menerapkan QueryFilter


Arti dari konsep tersebut adalah menggunakan kelas terpisah untuk setiap entitas yang memetakan metode ke setiap bidang untuk penyaringan.

Saring berdasarkan permintaan:

 GET /posts?title=source&status=publish 

Untuk memfilter, kita akan memiliki kelas PostFilter dan metode title () dan status () . PostFilter akan memperluas kelas abstrak QueryFiler yang bertanggung jawab untuk mencocokkan metode kelas dengan parameter yang dikirimkan.

Metode berlaku ()

Kelas QueryFIlter memiliki metode apply () , yang bertanggung jawab untuk menggunakan filter yang ada di kelas anak PostFilter .

 namespace App\Http\Filters; use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\Request; abstract class QueryFilter { /** * @var Request */ protected $request; /** * @var Builder */ protected $builder; /** * @param Request $request */ public function __construct(Request $request) { $this->request = $request; } /** * @param Builder $builder */ public function apply(Builder $builder) { $this->builder = $builder; foreach ($this->fields() as $field => $value) { $method = camel_case($field); if (method_exists($this, $method)) { call_user_func_array([$this, $method], (array)$value); } } } /** * @return array */ protected function fields(): array { return array_filter( array_map('trim', $this->request->all()) ); } } 

Intinya adalah bahwa untuk setiap bidang yang melewati Permintaan kami memiliki metode terpisah di kelas filter anak (kelas PostFilter). Ini memungkinkan kami untuk menyesuaikan logika untuk setiap bidang filter.

Kelas PostFilter

Sekarang mari kita beralih ke membuat kelas PostFilter yang memperluas QueryFilter . Seperti disebutkan sebelumnya, kelas ini harus berisi metode untuk setiap bidang yang harus kita filter. Dalam kasus kami, metode judul ($ nilai) dan status ($ nilai)

 namespace App\Http\Filters; use Illuminate\Database\Eloquent\Builder; class PostFilter extends QueryFilter { /** * @param string $status */ public function status(string $status) { $this->builder->where('post_status', strtolower($status)); } /** * @param string $title */ public function title(string $title) { $words = array_filter(explode(' ', $title)); $this->builder->where(function (Builder $query) use ($words) { foreach ($words as $word) { $query->where('post_title', 'like', "%$word%"); } }); } } 

Di sini saya tidak melihat alasan untuk analisis terperinci dari masing-masing metode, pertanyaan yang cukup standar. Intinya adalah bahwa sekarang kita memiliki metode terpisah untuk setiap bidang dan kita dapat menggunakan logika apa pun yang kita butuhkan untuk membentuk permintaan.

Buat scopeFilter ()

Sekarang kita perlu mengikat desainer model dan permintaan

 /** * @param Builder $builder * @param QueryFilter $filter */ public function scopeFilter(Builder $builder, QueryFilter $filter) { $filter->apply($builder); } 

Untuk mencari, kita perlu memanggil metode filter () dan mengirimkan instance dari QueryFilter , dalam kasus PostFilter kami .

 $filteredPosts = Post::filter($postFilter)->get(); 

Dengan demikian, semua logika penyaringan diproses dengan memanggil metode filter ($ postFilter) , menyimpan pengontrol dari logika yang tidak perlu.

Untuk memfasilitasi penggunaan kembali, Anda dapat menempatkan metode scopeFilter di dalam sifat dan menggunakannya untuk setiap model yang perlu disaring.

 namespace App\Http\Filters; use Illuminate\Database\Eloquent\Builder; trait Filterable { /** * @param Builder $builder * @param QueryFilter $filter */ public function scopeFilter(Builder $builder, QueryFilter $filter) { $filter->apply($builder); } } 

Dalam Post menambahkan:

 class Post extends CorcelPost { use Filterable; 

Tetap menambahkan index () ke metode controller sebagai parameter PostFilter dan memanggil metode model filter () .

 class PostController extends Controller { /** * @param PostFilter $filter * @return \Illuminate\Http\Resources\Json\ResourceCollection */ public function index(PostFilter $filter) { $posts = Post::filter($filter)->limit(10)->get(); return PostResource::collection($posts); } } 

Itu saja. Kami memindahkan semua logika penyaringan ke kelas yang sesuai, mengamati prinsip tanggung jawab tunggal (S dalam sistem prinsip SOLID)

Kesimpulan


Pendekatan penerapan filter ini memungkinkan Anda untuk tetap menggunakan pengontrol tipis trailer, dan juga memfasilitasi penulisan tes.

Berikut ini contoh menggunakan PHP dan Laravel. Tapi seperti yang saya katakan, ini adalah konsep dan dapat bekerja dengan bahasa atau kerangka kerja apa pun.

Referensi


Source: https://habr.com/ru/post/id485520/


All Articles