在我們日常程式碼開發中,可能最常見的功能就是列表篩選了。通過不同的引數,返回符合條件的內容。下面我分享一下自己的過濾程式碼設計(其實是從 Laracasts 上學來的?)。
基本實現
假設我們有一個圖書的列表,想要進行篩選:
- 標題含有"我們"的
- 定價大於30元的
- 出版社是"大地出版社"的
- 按照 ID 進行倒序排列
針對這幾個條件我們可以很快的寫出下面的程式碼
public function index()
{
return Book::query()
->where('title', 'like', '%我們%')
->where('price', '>=', 30)
->where('publisher', '大地出版社')
->orderBy('id', 'DESC')
->get();
}
優化
雖然我們實現了需求,但是如果我們增加或者減少篩選條件呢,以及在請求引數存在的時候才進行過濾呢?我們將不斷的在上面的程式碼中更改,當條件過多的時候,我們的程式碼將會變成一團亂麻。
下面是我的優化方法。
BaseFilter
首先我們需要一個基礎的 QueryFilter,然後我們所有的Filter都繼承這個類。
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->filters() as $name => $value) {
if (method_exists($this, $name)) {
call_user_func_array([$this, $name], array_filter([$value]));
}
}
return $this->builder;
}
public function filters()
{
return $this->request->all();
}
}
這個類的程式碼很簡單,主要功能集中在 apply 函式中,我們檢查每個請求引數,如果這個方法存在,那麼呼叫對應的方法。下面結合具體的例項進行解釋
BookFilter
我們的 BookFilter 程式碼如下:
class BookFilter extends QueryFilter
{
public function title($title)
{
return $this->builder->where('title', 'like', "%{$title}%");
}
public function price($price)
{
return $this->builder->where('price', '>=', "%{$price}%");
}
public function publisher($publisher)
{
return $this->builder->where('publisher', $publisher);
}
}
在上面的程式碼中,我們可以通過 URL 的引數來進行動態查詢。
books?title=我們 // 查詢標題含有我們的圖書
books?title=我們&price=25 // 查詢標題含有我們並且價格大於25元的圖書
這種結構的程式碼將會很靈活的控制我們的過濾列表,並且我們的程式碼也很整潔。
完結
當然,如果我們只進行到這一步是沒法進行查詢的,因為我們還沒有地方呼叫 QueryFilter 中的 apply 方法。有一個絕佳的地方可以進行呼叫,那就是模型中的 scope 。
class Book extends Model
{
public function scopeFilter($query, QueryFilter $filters)
{
return $filters->apply($query);
}
}
最後,我們原來控制器的方法將改為下面的樣子:
public function index(BookFilter $filters)
{
return Book::filter($filters)->get();
}
這樣我們就完成了一個比較靈活的篩選列表了。
PS:不瞭解 scope 用法的可以看 這裡