Overview
Reusable Eloquent trait for dynamic query filters in Laravel applications.
Originally published on Jang Keyte's Blog.
This article covers Build a Filter Trait for Fast Laravel Query Filtering — practical notes from real-world web development experience.
Code Examples
<?php
namespace App\Traits;
use App\Filters\BaseFilter;
use Illuminate\Database\Eloquent\Builder;
trait Filterable
{
public function scopeFilter(Builder $builder, BaseFilter $filters, array $filterFields = ['*'], array $orderFields = [])
{
return $filters->apply($builder, $filterFields, $orderFields);
}
}
<?php
namespace App\Filters;
use App\Helpers\Common\StringHelper;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class BaseFilter
{
/**
* @var Request
*/
public $request;
/**
* @var array
*/
protected $filters;
/**
* @var array
*/
protected $search = [];
/**
* @var $builder
*/
protected $builder;
/**
* @var string|null
*/
protected $orderField = null;
/**
* @var string
*/
protected $orderType = 'desc';
/**
* @var $filterable
*/
protected $filterable;
/**
* QueryFilter constructor.
* @param Request $request
*/
public function __construct(Request $request)
{
$this->request = $request;
$this->filters = $this->request->all();
}
/**
* @param Builder $builder
* @param array $filterFields
* @param array $orderFields
* @return Builder
*/
public function apply(Builder $builder, array $filterFields, array $orderFields = [])
{
$this->builder = $builder;
$this->orderFields = $orderFields;
foreach ($this->filters as $name => $value)
{
$method = 'filter' . Str::studly($name);
if (is_null($value) || $value == '') {
continue;
}
if (method_exists($this, $method)) {
$this->{$method}($value);
continue;
}
if (empty($this->filterable) || !is_array($this->filterable)) {
continue;
}
if (in_array($name, $this->filterable)) {
$this->builder->where($name, $value);
continue;
}
if (key_exists($name, $this->filterable)) {
$this->builder->where($this->filterable[$name], $value);
continue;
}
}
return $this->builder;
}
}
<?php
namespace App\Filters;
class UserFilter extends BaseFilter
{
protected $filterable = [
'id',
'author_id',
'birth_day',
'gender',
];
public function filterName($name)
{
return $this->builder->where('name', 'like', '%' . $name . '%');
}
}
Read More
For the full Vietnamese version, switch language using the VI | EN toggle above, or visit the original post.
Tạo một Traits Filterable trong thư mục App\Traits đặt tên là Filterable
<?php
namespace App\Traits;
use App\Filters\BaseFilter;
use Illuminate\Database\Eloquent\Builder;
trait Filterable
{
public function scopeFilter(Builder $builder, BaseFilter $filters, array $filterFields = ['*'], array $orderFields = [])
{
return $filters->apply($builder, $filterFields, $orderFields);
}
}
Trait này sẽ được gọi ở mỗi model cần filter, tạo thêm một class base filter trong App\Filters đặt tên là BaseFilter
<?php
namespace App\Filters;
use App\Helpers\Common\StringHelper;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class BaseFilter
{
/**
* @var Request
*/
public $request;
/**
* @var array
*/
protected $filters;
/**
* @var array
*/
protected $search = [];
/**
* @var $builder
*/
protected $builder;
/**
* @var string|null
*/
protected $orderField = null;
/**
* @var string
*/
protected $orderType = 'desc';
/**
* @var $filterable
*/
protected $filterable;
/**
* QueryFilter constructor.
* @param Request $request
*/
public function __construct(Request $request)
{
$this->request = $request;
$this->filters = $this->request->all();
}
/**
* @param Builder $builder
* @param array $filterFields
* @param array $orderFields
* @return Builder
*/
public function apply(Builder $builder, array $filterFields, array $orderFields = [])
{
$this->builder = $builder;
$this->orderFields = $orderFields;
foreach ($this->filters as $name => $value)
{
$method = 'filter' . Str::studly($name);
if (is_null($value) || $value == '') {
continue;
}
if (method_exists($this, $method)) {
$this->{$method}($value);
continue;
}
if (empty($this->filterable) || !is_array($this->filterable)) {
continue;
}
if (in_array($name, $this->filterable)) {
$this->builder->where($name, $value);
continue;
}
if (key_exists($name, $this->filterable)) {
$this->builder->where($this->filterable[$name], $value);
continue;
}
}
return $this->builder;
}
}
Tạo một class UserFilter riêng cho model User kế thừa từ BaseFilter
<?php
namespace App\Filters;
class UserFilter extends BaseFilter
{
protected $filterable = [
'id',
'author_id',
'birth_day',
'gender',
];
public function filterName($name)
{
return $this->builder->where('name', 'like', '%' . $name . '%');
}
}
Cách sử dụng:
- Ở model User sử dụng use Traits Filterable
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Modules\TreeManager\src\Traits\Filterable;
class User extends Model
{
use Filterable;
protected $dates = ['deleted_at'];
lúc này ở model User sẽ call function scopeFilter ở Traits Filterable, có nghĩa là mỗi khi chúng ta gọi đến User::filter() thì Laravel sẽ hiểu được là đang gọi đến 1 local scope như ở trên. Mỗi lần gọi đến hàm filter cần truyền vào một instance của class UserFilter
/* Cách sử dụng */
$userFilter = new UserFilter($request);
User::filter($userFilter)->get();
Khi đó hàm scopeFilter trong trait chúng ta khai báo sẽ được gọi và gọi đến function apply.
public function scopeFilter(Builder $builder, QueryFilter $filters, array $filterFields = ['*'], array $orderFields = [])
{
return $filters->apply($builder, $filterFields, $orderFields);
}
Tham số $request từ form gửi lên bằng POST, class BaseFilter sẽ tự tách nó ra thành mảng chứa các biến và gán vào biến $filters trong contructor.
public function __construct(Request $request)
{
$this->request = $request;
$this->filters = $this->request->all();
}
Biến này sẽ được dùng để duyệt mảng trích xuất từng $name ra để làm scope và so sánh với biến $filterable là một array sẽ chứa các trường mong muốn filter theo điều kiện trong UserFilter, nếu tồn tại sẽ tiến hành xử lý thêm vào query builder, nếu không tồn tại sẽ next.
protected $filterable = [
'id',
'author_id',
'birth_day',
'gender',
];
Ví dụ: input gender gửi lên sẽ được duyệt và gọi scopeGender, nếu cần xử lý thêm trong query builder thì có thể tự tạo thêm các scope trong UserFilter
public function filterName($name)
{
return $this->builder->where('name', 'like', '%' . $name . '%');
}