抽象類不能直接通過new去實例化一個對象,要獲取抽象類的對象, 需要先用一個類繼承抽象類, 然后去實例化子類。
在我們日常代碼開發中,可能最常見的功能就是列表篩選了。通過不同的參數,返回符合條件的內容。下面我分享一下自己的過濾代碼設計(其實是從 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 。
注意:scopeFilter($query, QueryFilter $filters) 傳參是抽象類,並使用該類調用了方法,所以實際使用時要傳遞實例化的繼承類。
class Book extends Model
{
public function scopeFilter($query, QueryFilter $filters)
{
return $filters->apply($query);
}
}
最后,我們原來控制器的方法將改為下面的樣子:
public function index(BookFilter $filters)
{
return Book::filter($filters)->get();
}
這樣我們就完成了一個比較靈活的篩選列表了。
// 假設 book 關聯了author
// 這里是 Book.php
public function author()
{
return $this->belongsTo(Author::class);
}
// 那么Filter 中的方法可以這樣寫
// 這里是 BookFilter.php
public function author($name)
{
return $builder->whereHas('author' function($query) use($name) {
return $query->where('name', 'like', "%{$name}%");
});
}