چطور در لاراول به شکلی درست کوئری‌ خود را بر اساس URL Query String فیلتر کنیم

نمونه ای از کد غیر اصولی
نمونه ای از کد غیر اصولی

احتمالا با شرایطی مواجه شده‌اید که می‌خواستید بر اساس پارامترهایی که در query string آدرس وجود دارد، کوئری‌های خود را فیلتر کنید و بعد از انجام اینکار و توسعه بیشتر پروژه، با کدی شبیه به کد تصویر بالا مواجه شدید.

این روش خروجی لازم را به شما می‌دهد ولی روش اصولی یا اصطلاحا یک good practice نیست.

چرا روش بالا اصولی نیست ؟

زمانی که تعداد پارامترهای ‌query string افزایش پیدا می‌کند، تعداد این if ها در کد زیاد شده که این به خودی خود بعد از ماه‌ها توسعه پروژه، شما را با یک کد بزرگ که به سختی قابل نگهداری است روبرو می‌کند.

اشکال دیگری که در این کد وجود دارد این هست که اصل دوم از اصول SOLID را نقض می‌کند.

بطور خلاصه طبق اصل دوم سالید باید برنامه را طوری طراحی کنیم که برای اضافه کردن امکانی جدید، مجبور به دستکاری کدهای قبلی نباشیم.

در واقع اصل دوم سالید تلاش می‌کند ما را از دستکاری کدهایی که به درستی کار می‌کنند منع کند تا اشکالی در برنامه فعلی ایجاد نکنیم.

در ادامه توضیح اِشکال مربوط به تصویر ابتدای متن، هر بار که نیاز به فیلتر نمودن پارامتر جدیدی در query string باشد، مجبور هستیم در کدهای قبلی دست برده و تغییراتی در آن ایجاد کنیم، که احتمال تولید باگ نیز به همین ترتیب بالا می‌رود.

چه روشی بهتر است ؟

برای حل اشکالات بالا، باید روشی طراحی کرد که به ما امکان جداسازی منطق هر بخش از فیلترهای query string را بدهد، به شکلی که اگر نیاز به فیلتر جدیدی داشتیم، در قالب یک متد جدید (جدای از کدهای قبلی) بتوان این فیلتر را اضافه کرد و در نهایت تاثیرش را خروجی کوئری دیتابیس ببینیم.

به همین منظور پکیجی برای فریم‌ورک لاراول نوشته‌ام که علاوه بر ایجاد ساختار مورد نظر برای افزودن فیلترها، تعدادی فیلتر پر کاربرد نیز در آن تعبیه شده که در ادامه به توضیح آنها می‌پردازم.

پکیج Laravel Filter Query String

۱. ابتدا با دستور زیر پکیج را در پروژه خود نصب کنید:

$ composer require mehradsadeghi/laravel-filter-querystring 

۲. سپس ترِیتِ FilterQueryString را در مدل مورد نظر خود use کنید. همینطور پراپرتی‌ای به نام filters را به مدل خود اضافه کنید که حاوی آرایه‌ایست از فیلرتهای مورد نظر شما.

برای نمونه در مدل User چنین کدی خواهیم داشت:

use Mehradsadeghi\FilterQueryString\FilterQueryString;

class User extends Model
{
    use FilterQueryString;
    protected $filters = [];
    ...
}

۳. برای اِعمال فیلترها در کوئری خود، باید متد filter را در eloquent query استفاده کنید. برای نمونه:

User::select('name')->filter()->get();

فیلترهای موجود

  1. Sort
  2. Comparisons
  3. In
  4. Like
  5. Where clause

به منظور توضیح هر فیلتر، تصور کنید جدول users ی با این اطلاعات داریم:

و کوئری ما در هر مثال نیز به این ترتیب است:

User::filter()->get();

فیلتر Sort

این فیلتر در واقع معادل order by در sql است که به شکل منعطفی در پکیج FilterQueryString می‌توان از آن استفاده کرد.

قاعده استفاده:

?sort=field
?sort=field,sort_type
?sort[0]=field1&sort[1]=field2
?sort[0]=field1&sort[1]=field2,sort_type
?sort[0]=field1,sort_type&sort[1]=field2,sort_type

در فایل User.php:

protected $filters = ['sort'];

مثال:

https://example.com?sort=created_at

خروجی:

توجه داشته باشید که وقتی نوع sort را تعیین نمی‌کنید، این مقدار به طور پیش‌فرض asc خواهد بود.

مثالی دیگر:

https://example.com?sort[0]=age,desc&sort[1]=created_at,desc

خروجی:

فیلترهای Comparisons

این فیلترها که جنبه مقایسه‌ای دارند شامل ۶ مورد می‌باشند:

  1. greater
  2. greater_or_equal
  3. less
  4. less_or_equal
  5. between
  6. not_between

قاعده استفاده:

?greater=field,value
?greater_or_equal=field,value
?less=field,value
?less_or_equal=field,value
?between=field,value1,value2
?not_between=field,value1,value2

در فایل User.php:

protected $filters = [
    'greater',
    'greater_or_equal',
    'less',
    'less_or_equal',
    'between',
    'not_between'
];

مثالی از فیلتر greater:

https://example.com?greater=age,20

خروجی:

مثالی از فیلتر not_between:

https://example.com?not_between=age,21,30

خروجی:

فیلتر In

این فیلتر معادل where in در sql است.

قاعده استفاده:

?in=field,value1,value2,...

در فایل User.php:

protected $filters = ['in'];

مثال:

https://example.com?in=name,mehrad,reza

خروجی:

فیلتر Like

این فیلتر معادل

like '%value%'

در sql است.

قاعده استفاده:

?like=field,value
?like[0]=field1,value1&like[1]=field2,value2

در فایل User.php:

protected $filters = ['like'];

مثال:

https://example.com?like=name,meh

خروجی:

مثال:

https://example.com?like[0]=name,meh&like[1]=username,dar

خروجی:

فیلتر پیش‌فرض Where Clause

بطور کلی زمانی که در query string یکی از فیلترهای معرفی شده در بالا موجود نباشد، پارامتر و مقدار آن در where کوئری قرار می‌گیرد. این فیلتر مناسب زمانی‌ست که می‌خواهید یکی از ستون‌های جدول خود را مستقیما فیلتر کنید.

قاعده استفاده:

?field=value
?field1=value&field2=value
?field1[0]=value1&field1[1]=value2
?field1[0]=value1&field1[1]=value2&field2[0]=value1&field2[1]=value2

تصور کنید می‌خواهیم ستون‌های name ، username و age را فیلتر کنیم.

در فایل User.php:

protected $filters = ['name', 'username', 'age'];

مثال:

https://example.com?name=mehrad

خروجی:

مثال:

https://example.com?age=22&username=dariush123

خروجی:

مثال:

https://example.com?name[0]=mehrad&name[1]=dariush

خروجی:

مثال:

https://example.com?name[0]=mehrad&name[1]=dariush&username[0]=mehrad123&username[1]=reza1234

خروجی:

فیلترهای سفارشی

از طریق فیلترهای سفارشی شما این امکان را دارید تا فیلترهای خود را در قالب متدهایی در مدل خود تعریف کنید. این امکان به رعایت اصل دوم سالید کمک می‌کند، به این صورت که به ازای هر فیلتر جدید، متدی جدید می‌نویسیم و نیازی به ایجاد تغییر در کدهای قبلی نیست.

برای نمونه فرض کنید فیلتری با نام all_except نیاز داریم که تمام کاربران به بجز کاربری که نام آن وارد می‌شود را بر‌می‌گرداند:

در فایل User.php:

protected $filters = ['all_except'];

public function all_except($query, $value) {
    $query->where('name', '!=', $value);
}

برای آزمایش فیلتر اضافه شده:

https://example.com?all_except=mehrad
توجه کنید که متدهایی که به عنوان فیلتر تعریف می‌کنید بالاترین اولویت را دارند و حتی این امکان وجود دارد که فیلترهای موجود را override کنید.

برای نمونه می‌خواهیم فیلتر in را به گونه‌ای تغییر دهیم که فقط و فقط سه مقدار را بپذیرد:

در فایل User.php:

protected $filters = ['in'];

public function in($query, $value) {
    $exploded = explode(',', $value);
    if(count($exploded) != 4) {
        // throwing an exception or whatever you like to do
    } 
    $field = array_shift($exploded);
    $query->whereIn($field, $exploded);
}

نمونه‌ای دیگر از کاربردهای خوب فیلترهای سفارشی زمانی‌ست که می‌خواهید نام ستون‌های جدول را مستقیما به کاربر (در query string) نمایش ندهید.

برای نمونه فرض کنید می‌خواهیم بجای username از by استفاده کنیم:

در فایل User.php:

protected $filters = ['by'];

public function by($query, $value) {
    $query->where('username', $value);
}

سپس:

https://example.com?by=dariush123

خروجی:

به عنوان توصیه برای جلوگیری از شلوغ شدن مدل از متدهای فیلترینگ، می‌توانید trait ی ساخته و فیلترهای آن مدل را در آن قرار دهید.

همینطور می‌توانید در گیت‌هاب پکیج Laravel Filter Query String را مشاهده کنید.