أريد أن أوجه انتباهكم إلى مفهوم تنظيم التصفية حسب طلب عنوان URL. على سبيل المثال ، سأستخدم لغة PHP وإطار Laravel.
مفهوم
تتمثل الفكرة في إنشاء فئة
QueryFilter عالمية للعمل مع المرشحات.
GET /posts?title=source&status=active
باستخدام هذا المثال ، سنقوم بتصفية
المنشورات (نموذج النشر) وفقًا للمعايير التالية:
- وجود كلمة "مصدر" في حقل العنوان ؛
- القيمة "نشر" في حقل الحالة ؛
مثال التطبيق
آخر نموذج
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Post extends Model { 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 { 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 { public function index() { $posts = Post::limit(10)->get(); return PostResource::collection($posts); } }
يتم تنظيم التصفية القياسية بواسطة الكود التالي:
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 { 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()) ); } }
خلاصة القول هي أنه بالنسبة لكل حقل يتم تمريره عبر
طلب ، لدينا طريقة منفصلة في فئة التصفية الفرعية (فئة PostFilter). هذا يسمح لنا بتخصيص المنطق لكل حقل تصفية.
فئة PostFilterالآن دعنا ننتقل إلى إنشاء فئة
PostFilter تمتد
QueryFilter . كما ذكرنا سابقًا ، يجب أن تحتوي هذه الفئة على طرق لكل حقل يتعين علينا تصفيته. في حالتنا ، طرق
(القيمة بالقيمة) والحالة (القيمة بالقيمة) 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%"); } }); } }
لا أرى هنا أي سبب لإجراء تحليل مفصل لكل طريقة من الطرق ، استفسارات قياسية تمامًا. النقطة المهمة هي أن لدينا الآن طريقة منفصلة لكل حقل ، ويمكننا استخدام أي منطق نحتاجه لتشكيل الطلب.
إنشاء scopeFilter ()نحن الآن بحاجة إلى ربط مصمم النموذج والاستعلام
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 { public function scopeFilter(Builder $builder, QueryFilter $filter) { $filter->apply($builder); } }
في
المشاركة أضف:
class Post extends CorcelPost { use Filterable;
يبقى لإضافة
فهرس () إلى أسلوب وحدة التحكم كمعلمة
PostFilter ثم استدعاء الأسلوب طراز
filter () .
class PostController extends Controller { public function index(PostFilter $filter) { $posts = Post::filter($filter)->limit(10)->get(); return PostResource::collection($posts); } }
هذا كل شيء. لقد نقلنا كل منطق التصفية إلى الفئة المناسبة ، مع مراعاة مبدأ المسؤولية الفردية (S في نظام مبادئ SOLID)
استنتاج
يسمح لك هذا النهج في تطبيق المرشحات بالالتزام بوحدة التحكم في المقطورة الرقيقة ، كما يسهل كتابة الاختبارات.
هنا مثال باستخدام PHP و Laravel. لكن كما قلت ، هذا مفهوم ويمكنه العمل مع أي لغة أو إطار.
مراجع