Laravel 模型過濾(Filter)設計

Epona發表於2019-05-29

在我們日常程式碼開發中,可能最常見的功能就是列表篩選了。通過不同的引數,返回符合條件的內容。下面我分享一下自己的過濾程式碼設計(其實是從 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 用法的可以看 這裡

There's nothing wrong with having a little fun.

相關文章