[Laravel] Hướng dẫn cách Query Filter đơn giản trong Laravel
Query filter là một vấn đề khá quen thuộc cho dù bạn code ngôn ngữ hay nền tảng nào. Các form search dữ liệu nâng cao với field, column cần filter là chức năng thông thường của website. Nhưng dường như để làm nó một cách rõ ràng, dễ dàng mở rộng và tái sử dụng thì không đơn giản. Trong bài viết này, mình sẽ hướng dẫn query filter cho laravel. Bạn sẽ thấy rằng, nó không khó quá đâu.
Cách làm thông thường
public function index(Request $request)
{
$user = User::query();
if ($request->has('name')) {
$user->where('name', 'LIKE', '%' . $request->name . '%');
}
if ($request->has('status')) {
$user->where('status', $request->status);
}
if ($request->has('birthday')) {
$user->whereDate('birthday', $request->birthday);
}
return $user->get();
}
Phía trên là code với cách làm thông thường, rất dễ để viết, người đọc cũng không khó khăn để hiểu. Nhưng nhược điểm của cách này rất nhiều: khó kiểm soát nếu có nhiều field, khó tái sử dụng, phải lặp đi lặp lại việc kiểm tra điều kiện if... Ta sẽ đi giải quyết những vấn đề đó.
Trước hết là việc khó tái sử dụng, ta sẽ sử dụng local scopes. Xây dựng các hàm scope trong model User
public function scopeName($query, $request)
{
if ($request->has('name')) {
$query->where('name', 'LIKE', '%' . $request->name . '%');
}
return $query;
}
public function scopeStatus($query, $request)
{
if ($request->has('status')) {
$query->where('status', $request->status);
}
return $query;
}
public function scopeBirthday($query, $request)
{
if ($request->has('birthday')) {
$query->whereDate('birthday', $request->birthday);
}
return $query;
}
Khi đó việc filter đã trở nên dễ nhìn hơn (dù chưa clean)
public function index(Request $request)
{
$user = User::query()
->name($request)
->status($request)
->birthday($request);
return $user->get();
}
Giả sử chúng ta muốn filter thêm field gender
thì sao? Tất nhiên là thêm hàm scope vào Model
public function scopeGender($query, $request)
{
if ($request->has('gender')) {
$query->where('gender', $request->gender);
}
return $query;
}
rồi sử dụng nó để filter
public function index(Request $request)
{
$user = User::query()
->name($request)
->status($request)
->birthday($request)
->gender($request);
return $user->get();
}
Nhưng mà nếu việc filter đó dùng nhiều nơi thì phải đi tìm và thêm filter gender
đó vào sao? Ơ... ơ... thì... chịu khó tìm và thêm vào thôi +_+
Hàm scope filter
Để tránh việc mỗi lần filter một field mới là phải đi "lùng sục" và thêm code ở khắp nơi, ta sẽ viết một hàm scope filter chịu trách nhiệm gọi các hàm filter field một cách tự động tùy vào tham số truyền vào. Ý tưởng là User
sẽ được sử dụng như sau
public function index(Request $request)
{
$user = User::filter($request);
return $user->get();
}
Hàm filter không nên khai báo nhiều lần, nó chỉ là hàm "trung chuyển" gọi các hàm filter của mỗi model. Ta sẽ triển khai nó bằng trait để các model có thể dùng chung.
trait Filterable
{
public function scopeFilter($query, $request)
{
//
}
}
đồng thời use Filterable User
class User extends Authenticatable
{
use Filterable;
...
}
Giờ ta sẽ tìm cách viết hàm filter sao cho nó có thể tự động gọi các hàm filter field của chính model đó. Bắt đầu bằng việc duyệt các tham số mà request truyền vào
public function scopeFilter($query, $request)
{
$param = $request->all();
foreach ($param as $field => $value) {
//
}
return $query;
}
Cấu trúc của param bây giờ là
array:4 [
"status" => "1"
"gender" => "0"
"name" => "reishou"
"birthday" => "2000-01-01"
]
Ở đây lưu ý là không thể dùng các hàm scope field đã có, trong User
ta sẽ đổi tên các hàm scopeName
, scopeStatus
... thành filterName
, filterStatus
... cũng như thay đổi chút nội dung.
public function filterName($query, $value)
{
return $query->where('name', 'LIKE', '%' . $value . '%');
}
public function filterStatus($query, $value)
{
return $query->where('status', $value);
}
public function filterBirthday($query, $value)
{
return $query->whereDate('birthday', $value);
}
public function filterGender($query, $value)
{
return $query->where('gender', $value);
}
Quay lại Filterable
, thêm nội dung cho hàm filter
public function scopeFilter($query, $request)
{
$param = $request->all();
foreach ($param as $field => $value) {
$method = 'filter' . Str::studly($field);
if (method_exists($this, $method)) {
$this->{$method}($query, $value);
}
}
return $query;
}
Vòng lặp sẽ duyệt từng phần tử của $param
, dựa theo $field
để gọi hàm tương ứng bên User
. Biến $param
chỉ chứa các $field
có trong $request
nên không cần phải sử dụng $request->has('name')
nữa. Nhưng vẫn còn một chú ý nho nhỏ là cần loại trừ trường hợp $value là chuỗi rỗng ''
public function scopeFilter($query, $request)
{
$param = $request->all();
foreach ($param as $field => $value) {
$method = 'filter' . Str::studly($field);
if ($value != '') {
if (method_exists($this, $method)) {
$this->{$method}($query, $value);
}
}
}
return $query;
}
Đến đây có thể gọi là tạm đủ cho chức năng filter, nhưng liệu còn có thể làm thêm gì đó không?
Phải lặp lại nhiều lần? Hãy refactor nó.
Nếu nhìn lại các hàm filter field ở User
, ta sẽ thấy có những hàm kiểu như sau
public function filterStatus($query, $value)
{
return $query->where('status', $value);
}
public function filterGender($query, $value)
{
return $query->where('gender', $value);
}
nó có điểm chung là đơn giản, chỉ "where" field bằng giá trị truyền vào. Ý tưởng là hàm filter
sẽ thực hiện query tự động theo field luôn, không cần khai báo hàm filter ở model nữa. Nhưng model lại cần có cái gì đó để hàm filter
nhận biết những field đó.
Tạo biến $filterable
ở User
protected $filterable = [
'status',
'gender'
];
Bổ sung thêm code cho hàm filter
public function scopeFilter($query, $request)
{
$param = $request->all();
foreach ($param as $field => $value) {
$method = 'filter' . Str::studly($field);
if ($value != '') {
if (method_exists($this, $method)) {
$this->{$method}($query, $value);
} else {
if (!empty($this->filterable) && is_array($this->filterable)) {
if (in_array($field, $this->filterable)) {
$query->where($this->table . '.' . $field, $value);
}
}
}
}
}
return $query;
}
Bởi vì không chắc model có khai báo biến $filterable
nên ta cần check điều kiện trước khi thực hiện query. Tuy nhiên đến đây ta nhận ra rằng có thể có trường hợp $field
truyền vào không giống với field trong database. Ví dụ trong bảng user không có column gender
mà là sex
chẳng hạn, ta sẽ thay đổi một chút trong User
protected $filterable = [
'status',
'gender' => 'sex'
];
Cập nhật thêm cho hàm filter
public function scopeFilter($query, $request)
{
$param = $request->all();
foreach ($param as $field => $value) {
$method = 'filter' . Str::studly($field);
if ($value != '') {
if (method_exists($this, $method)) {
$this->{$method}($query, $value);
} else {
if (!empty($this->filterable) && is_array($this->filterable)) {
if (in_array($field, $this->filterable)) {
$query->where($this->table . '.' . $field, $value);
} elseif (key_exists($field, $this->filterable)) {
$query->where($this->table . '.'
. $this->filterable[$field], $value);
}
}
}
}
}
return $query;
}
Clean code
Ta sẽ làm cho hàm filter
nhìn ổn hơn, "đẹp" hơn bằng cách loại trừ biến $request
, không còn lệ thuộc vào nó nữa, chỉ truyền vào mảng $param
.
public function index(Request $request)
{
$param = $request->all();
$user = User::filter($param);
return $user->get();
}
Ngoài ra vận dụng thêm Early Return
với hàm filter
public function scopeFilter($query, $param)
{
foreach ($param as $field => $value) {
$method = 'filter' . Str::studly($field);
if ($value === '') {
continue;
}
if (method_exists($this, $method)) {
$this->{$method}($query, $value);
continue;
}
if (empty($this->filterable) || !is_array($this->filterable)) {
continue;
}
if (in_array($field, $this->filterable)) {
$query->where($this->table . '.' . $field, $value);
continue;
}
if (key_exists($field, $this->filterable)) {
$query->where($this->table . '.' . $this->filterable[$field], $value);
continue;
}
}
return $query;
}
Sử dụng
Như vậy là chúng ta đã triển khai xong một "cơ chế" filter đơn giản. Từ bây giờ, nếu muốn filter cho một model nào, chỉ cần use Filterable
, các field đơn giản thì khai báo trong biến $filterable
, với các logic phức tạp hơn thì tạo các hàm filter{$field}()
tương ứng. Sau đó chỉ cần đơn giản gọi hàm scope filter($param)
là xong.
Đánh giá
Ưu điểm:
Cách dùng gọn gàng, dễ tái sử dụng, dễ mở rộng thêm các field mới. Có thể sử dụng hàm filter
chung với các hàm scope khác cũng như các hàm gốc của model theo syntax chaining method.
$user = User::filter($param)
->popular()
->vipMember();
Nhược điểm:
Khó nắm được chính xác thứ gì sẽ được filter, phải kiểm soát kỹ đầu vào trước khi gọi hàm filter. Không dễ tiếp cận đối với fresher.
Theo dõi VnCoder trên Facebook, để cập nhật những bài viết, tin tức và khoá học mới nhất!
Bài viết liên quan
Bài viết mới
Được xem nhiều nhất
[Laravel] Hướng dẫn tích hợp thanh toán online, tích hợp cổng thanh to...
[Laravel] Sử dụng Ajax làm chức năng tìm kiếm trong Laravel
[Laravel] Cách sử dụng Charts - hướng dẫn vẽ biểu đồ trong Laravel
[Laravel] Hướng dẫn sử dụng Raw DB Query trong Laravel
[Laravel] Hướng dẫn đăng nhập, đăng ký tài khoản bằng tài khoản Facebo...
Khóa học liên quan
Xây dựng ứng dụng với Laravel và Vuejs
Lượt xem: 16225
Chuyên mục: Laravel