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 { 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 { 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 { public function index() { $posts = Post::limit(10)->get(); return PostResource::collection($posts); } }
Pemfilteran standar diatur oleh kode berikut:
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 { protected $request; protected $builder; public function __construct(Request $request) { $this->request = $request; } 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); } } } 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 PostFilterSekarang 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 { public function status(string $status) { $this->builder->where('post_status', strtolower($status)); } 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
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 { 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 { 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