QueryFilter: مفهوم نماذج الترشيح

أريد أن أوجه انتباهكم إلى مفهوم تنظيم التصفية حسب طلب عنوان URL. على سبيل المثال ، سأستخدم لغة PHP وإطار Laravel.

مفهوم


تتمثل الفكرة في إنشاء فئة QueryFilter عالمية للعمل مع المرشحات.

GET /posts?title=source&status=active 

باستخدام هذا المثال ، سنقوم بتصفية المنشورات (نموذج النشر) وفقًا للمعايير التالية:

  • وجود كلمة "مصدر" في حقل العنوان ؛
  • القيمة "نشر" في حقل الحالة ؛

مثال التطبيق


آخر نموذج

 <?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', ]; } 

أضف طريقًا:

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

قم بإنشاء ملف Resource \ Post للإخراج بتنسيق 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, ]; } } 

وحدة تحكم نفسها مع عمل واحد واحد:

 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); } } 

يتم تنظيم التصفية القياسية بواسطة الكود التالي:

 /** * @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); } 

مع هذا النهج ، نواجه نمو وحدة التحكم ، وهو أمر غير مرغوب فيه.

تطبيق QueryFilter


معنى هذا المفهوم هو استخدام فئة منفصلة لكل كيان يقوم بتعيين الطرق لكل حقل للتصفية.

تصفية حسب الطلب:

 GET /posts?title=source&status=publish 

من أجل التصفية ، سيكون لدينا فئة PostFilter وطريقتا () و status () . سيقوم PostFilter بتمديد QueryFiler للفئة التجريدية وهو المسؤول عن مطابقة أساليب الفصل مع المعلمات التي تم تمريرها.

طريقة تطبيق ()

لدى فئة QueryFIlter طريقة application () ، وهي المسؤولة عن استدعاء عوامل التصفية الموجودة في الفئة الفرعية 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()) ); } } 

خلاصة القول هي أنه بالنسبة لكل حقل يتم تمريره عبر طلب ، لدينا طريقة منفصلة في فئة التصفية الفرعية (فئة PostFilter). هذا يسمح لنا بتخصيص المنطق لكل حقل تصفية.

فئة PostFilter

الآن دعنا ننتقل إلى إنشاء فئة PostFilter تمتد QueryFilter . كما ذكرنا سابقًا ، يجب أن تحتوي هذه الفئة على طرق لكل حقل يتعين علينا تصفيته. في حالتنا ، طرق (القيمة بالقيمة) والحالة (القيمة بالقيمة)

 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%"); } }); } } 

لا أرى هنا أي سبب لإجراء تحليل مفصل لكل طريقة من الطرق ، استفسارات قياسية تمامًا. النقطة المهمة هي أن لدينا الآن طريقة منفصلة لكل حقل ، ويمكننا استخدام أي منطق نحتاجه لتشكيل الطلب.

إنشاء scopeFilter ()

نحن الآن بحاجة إلى ربط مصمم النموذج والاستعلام

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

للبحث ، نحتاج إلى استدعاء طريقة filter () وتمرير مثيل QueryFilter ، في حالتنا PostFilter .

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

وبالتالي ، تتم معالجة كل منطق التصفية عن طريق استدعاء طريقة التصفية (post postilter) ، مع توفير وحدة التحكم من المنطق غير الضروري.

لتسهيل إعادة الاستخدام ، يمكنك وضع طريقة filterFilter في السمة واستخدامها لكل نموذج يحتاج إلى تصفية.

 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); } } 

في المشاركة أضف:

 class Post extends CorcelPost { use Filterable; 

يبقى لإضافة فهرس () إلى أسلوب وحدة التحكم كمعلمة PostFilter ثم استدعاء الأسلوب طراز 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); } } 

هذا كل شيء. لقد نقلنا كل منطق التصفية إلى الفئة المناسبة ، مع مراعاة مبدأ المسؤولية الفردية (S في نظام مبادئ SOLID)

استنتاج


يسمح لك هذا النهج في تطبيق المرشحات بالالتزام بوحدة التحكم في المقطورة الرقيقة ، كما يسهل كتابة الاختبارات.

هنا مثال باستخدام PHP و Laravel. لكن كما قلت ، هذا مفهوم ويمكنه العمل مع أي لغة أو إطار.

مراجع


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


All Articles