algolia search

Tìm thấy x bài viết trong xms.

Laravel Eloquent Builder Macro: phù phép whereLike trong dự án thực tế

Laravel Eloquent Builder Macro: phù phép whereLike trong dự án thực tế


Vẽ vời một chút

Trong project thực tế chuyện tìm kiếm thứ gì đó trong database bằng LIKE xảy ra như cơm bữa.

Ví dụ tìm về bảng users với Model là User nha

User::query()
   ->where('name', 'LIKE', "%{$searchTerm}%") 
   ->orWhere('email', 'LIKE', "%{$searchTerm}%") 
   ->get();

Và bạn sẽ còn tìm ở nhiều nơi nữa, Post, Comment, Category, Tag, ... và bạn chán ngấy giống như mấy thằng sở khanh nó ngấy con ghẹ của nó trong khi bạn vẫn FA =)) vậy thì đổi cơn gió mùa xem sao

Sử dụng Laravel Eloquent Builder Macro

Bạn hãy mở App\Providers\AppServiceProvider lên, trong function boot() thêm đoạn này vào

use Illuminate\Database\Eloquent\Builder;

// ...

Builder::macro('whereLike', function(string $attribute, string $searchTerm) {
   return $this->orWhere($attribute, 'LIKE', "%{$searchTerm}%");
});

Với đoạn search ở trên bạn có thể dễ dàng viết lại

User::query()
   ->whereLike('name', $searchTerm)
   ->whereLike('name', $searchTerm)
   ->get();

Ơ khoan, cải tiến một chút...

Nếu như viết ở trên thì có mẹ gì đâu mà phải vẽ vời đúng không nào =))

Bạn sẽ cần search nhiều field với một dữ liệu đầu vào là searchTeam, ta viết lại đoạn macro

Builder::macro('whereLike', function($attributes, string $searchTerm) {
   foreach(array_wrap($attributes) as $attribute) {
      $this->orWhere($attribute, 'LIKE', "%{$searchTerm}%");
   }
   
   return $this;
});

array_wrap quả thật là một nice helper, bạn nạp vào array nó trả về array, bạn nạp vào string nó cũng trả về array, và lặp, không phải ngại.

Vậy đoạn search ở trên được viết lại như sau

// search một field
User::whereLike('name', $searchTerm)->get();

// search nhiều field
User::whereLike(['name', 'email'], $searchTerm)->get();

Chờ một chút, có bug đấy 👻

Bạn định tìm kiếm một userrole là adminname hoặc email là chung, à ha nhanh chóng bạn sẽ viết ngay

User::query()
   ->where('role', 'admin')
   ->whereLike(['name', 'email'], 'chung')
   ->get();

À ha, cuộc đời bạn sẽ ngập ngụa trong bug ngay 💩💩 where ở trên sử dụng AND, whereLike sử dụng OR, và chẳng khác gì bạn tự SQL Injection chính mình đâu.

Cải tiến chút nữa coi ~

Builder::macro('whereLike', function ($attributes, string $searchTerm) {
    $this->where(function (Builder $query) use ($attributes, $searchTerm) {
        foreach (array_wrap($attributes) as $attribute) {
            $query->orWhere($attribute, 'LIKE', "%{$searchTerm}%");
        }
    });

    return $this;
});

Đấy ~ bạn nào giỏi tìm ra bug coi 😁😁

Đừng tự mãn thế chứ, làm gì cho ngầu chút đi 🙄🤔🤔

Hỗ trợ search các bảng quan hệ

Talk is cheap, show me the code, OK

Builder::macro('whereLike', function ($attributes, string $searchTerm) {
    $this->where(function (Builder $query) use ($attributes, $searchTerm) {
        foreach (array_wrap($attributes) as $attribute) {
            $query->when(
                str_contains($attribute, '.'),
                function (Builder $query) use ($attribute, $searchTerm) {
                    [$relationName, $relationAttribute] = explode('.', $attribute);

                    $query->orWhereHas($relationName, function (Builder $query) use ($relationAttribute, $searchTerm) {
                        $query->where($relationAttribute, 'LIKE', "%{$searchTerm}%");
                    });
                },
                function (Builder $query) use ($attribute, $searchTerm) {
                    $query->orWhere($attribute, 'LIKE', "%{$searchTerm}%");
                }
            );
        }
    });

    return $this;
});

Giờ thì bạn có thể search cả relation, ví dụ tìm một bài post mà keyword có thể là name, text (content), tên tác giả, tags có trong bài viết đó

Post::whereLike(['name', 'text', 'author.name', 'tags.name'], $searchTerm)->get();

Hết rồi ~ rảnh đọc kết chơi

Kết

Đọc tới đây hẳn bạn sẽ thấy ghen tị với Chung phải không, dễ hiểu mà độ đệp trai thanh lịch của Chung mấy ai mà không sinh lòng ganh ghét cơ chứ 😄😄😁 - có gì đó sai nhưng mà thôi, bỏ qua đi. Bài viết lấy nguồn từ murze.be được Chung - (đáng lẽ ra là nhà văn nổi tiếng nhưng vì bố mẹ bắt đi lập trình) dịch và thêm muối cho vừa miệng

🎁🎁 💎 🎁🎁 

Đánh giá bài viết

Thích thì like
Laravel Eloquent Builder Macro: phù phép whereLike trong dự án thực tế
5/5 2 votes

Bình luận

Nguyễn Duy Nguyên avatar
Nguyễn Duy Nguyên
Em thấy hay mà đọc xong cũng khó hiểu quá bác Chung :D

Chung Nguyễn avatar
Chung Nguyễn
Khó hiểu hè, copy về xài thử đi đã
Quốc Cường Nguyễn avatar
Quốc Cường Nguyễn

Cho em hỏi xíu, em áp dụng thử thì ra lỗi "Class 'App\ProductDetails' not found"

Theo em hiểu thì là  do project này em để model trong thư mục app/Models. Vậy làm sao custom lại để Builder hiểu tìm models ở trong thư mục "app/Models" ạ. Hoặc có nếu em hiểu sai thì giải quyết như thế nào trong trường hợp này ạ.
Chung Nguyễn avatar
Chung Nguyễn
Có 2 cách em nhé:
  1. import nó ở đầu file php bằng: use App\Models\ProductDetails;
  2. gọi full namespace nó ra: \App\Models\ProductDetails::whereLike('name', 'name')->get();
Nguyễn Văn Vũ avatar
Nguyễn Văn Vũ
Bạn cho mình hỏi làm sao để chống nhân bản code, kiểu như có source nhưng chưa chắc đã deploy được lên product. Thanks
Chung Nguyễn avatar
Chung Nguyễn
PHP là mã nguồn mở, ở nước ngoài thì LICENSE ghi rõ quyền hạn người sử dụng được phép làm gì với source, ở VN thì hơi khoai vụ này, nếu bạn không cho người khác đọc và sửa code thì dùng phương pháp: Encryption (mã hóa) và Obfuscation (làm cho trở nên khó đọc, khó sửa..) việc này phải dùng tool http://www.zend.com/en/products/zend-guard là một ví dụ, bạn có thể tìm thêm.
Còn nếu vẫn cho người dùng đọc chỉnh sửa code, thì chắc bạn sẽ mã hóa một vài file quan trọng nào đó, thiếu nó hệ thống sẽ k chạy được, file đó bạn tùy ý cấu hình.
Lê Xuân Bình avatar
Lê Xuân Bình

Cho mình hỏi ngủ xíu:

ở đoạn code đầu tiên

User::query()
   ->where('name', 'LIKE', "%{$searchTerm}%") 
   ->orWhere('email', 'LIKE', "%{$searchTerm}%") 
   ->get();

Mình viết lại là

User::->where('name', 'LIKE', "%{$searchTerm}%") 
   ->orWhere('email', 'LIKE', "%{$searchTerm}%") 
   ->get();

nó vẫn chạy, vậy query() đó dùng để làm gì vậy bạn ad? 

Chung Nguyễn avatar
Chung Nguyễn

Laravel sử dụng rất nhiều magic method, bản chất khi bạn gọi đến method where() Eloquent Model không hề có method này, nó tiến hành kích hoạt new Query Builder, vậy tác dụng của query() là kích hoạt new Query Builder để tạo câu lệnh SQL, và hệ thống bớt đi một bước xử lý.

Nguyen Manh avatar
Nguyen Manh

Cảm ơn tác giả, rất hay, mình sẽ thử áp dụng cho project hiện tại ^^

Nhân tiện mình đang có 1 vài vấn đề bác có thể hướng dẫn qua được không nhỉ ? 

- 1 là orderBy với relationship được không nhỉ ? Ví dụ mình lấy ra Post::with(['author']), tuy nhiên mình lại muốn sắp sếp với author.name chẳng hạn.

- 2 là search like dữ liệu với mysql có cả tiếng Nhật, và yêu cầu là search phân biệt hiragana/katakana, nếu là alphabet thì phân biệt chữ hoa chữ thường, search đc cả ký tự đặc biệt kiểu như "&" và "%". Hiện tại giải phải của mình đang là để charset của field cần search like là "ujis", khi search thì sẽ thêm ký tự "\" trước param search để search được "%", và thêm UPPER($field) vào whereRaw. tuy nhiên mình thấy đang hơi cồng kềnh, và đi sửa thì cũng hơi mệt vì project khá rộng.


Cảm ơn bác :D

Chung Nguyễn avatar
Chung Nguyễn

1. Được bạn nhé. Bạn sử dụng Closure (callback function cho relations)

Post::with([
    'author' => function ($query) {
        $query->orderBy('name');
    }])->get();

2. Mình có làm một số dự án JP nhưng chưa có case phải search này nên chưa thể giúp gì cho bạn.

Trường hợp này bạn có thể dùng SearchEngine để tối ưu hơn nhé.
- Algolia
- Elastic Search

Hiển thị bình luận Facebook