簡化我們做後臺時對列表的篩選程式碼

琳琅天上發表於2020-12-02

在寫後臺介面的時候,我們會使用到大量的篩選,比如狀態篩選、時間篩選、關鍵篩選等等等。
起初在控制器中我是習慣:

..
..

public function index(Request $request)
{
    $user = User::query();

    if($status = $request->input('status'))
    {

    }

    if($isAdmin = $request->input('xxx'))
    {

    }

    ....

    return $user->paginate(xxx);
}

後面隨著需要篩選的欄位越來越多,又或者我的後臺比較簡單,基本都是隻有關鍵字篩選,這樣我們就是一直在寫同樣的程式碼。

下面我們利用 區域性作用域 來改善一下。

首先我們定義一個 trait:

<?php

namespace xxx;

use App\Filters\Filter;
use Illuminate\Database\Eloquent\Builder;


/**
 * Trait FilterScopeTrait
 *
 * @package App\Models\Traits
 */
trait FilterScopeTrait
{
    /**
     * @param  \Illuminate\Database\Eloquent\Builder  $builder
     * @param  \App\Filters\Filter                    $filter
     *
     * @return \Illuminate\Database\Eloquent\Builder
     *
     * @author   : stringer <10******56@qq.com>
     * @datetime : 2020/11/24 10:15
     */
    public function scopeFilter(Builder $builder, Filter $filter)
    {
        return $filter->filter($builder);
    }
}

然後我們將這個 trait 在模型中引入。我這邊圖方便就直接定義一個基類 Model

<?php

namespace App\Models;

use xxx\FilterScopeTrait;
use Illuminate\Database\Eloquent\Model as BaseModel;

/**
 * @method static \Illuminate\Database\Eloquent\Builder|static filter(\App\Filters\Filter $filter)
 *
 * @package App\Models
 */
class Model extends BaseModel
{
    use FilterScopeTrait;
}

讓我們的 User 繼承新的 Model

<?php

namespace App\Models;

/**
 * @method static Builder|User filter(\App\Filters\Filter $filter)
 */
class User extends Model
{
    ...
    ...
    ...
}

定義 Filter

<?php

namespace App\Filters;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;

/**
 * Class Filter
 *
 * @package App\Filters
 */
abstract class Filter
{
    /**
     * @var \Illuminate\Database\Eloquent\Builder
     */
    protected $builder;

    /**
     * @var \Illuminate\Http\Request
     */
    protected $request;

    /**
     * @var array
     */
    protected $filters = [];


    /**
     * Filter constructor.
     *
     * @param  \Illuminate\Http\Request  $request
     */
    public function __construct(Request $request)
    {
        $this->request = $request;
    }


    /**
     * 篩選項
     *
     * @return array
     *
     * @author   : stringer <10******56@qq.com>
     * @datetime : 2020/11/24 9:53
     */
    protected function getFilters(): array
    {
        return $this->request->only($this->filters);
    }


    /**
     * @param  \Illuminate\Database\Eloquent\Builder  $builder
     *
     * @return \Illuminate\Database\Eloquent\Builder
     *
     * @author   : stringer <10******56@qq.com>
     * @datetime : 2020/11/24 10:02
     */
    public function filter(Builder $builder)
    {
        $this->builder = $builder;

        foreach ($this->getFilters() as $filter => $value) {
            if (method_exists($this, $filter)) {
                $this->$filter($value);
            }
        }

        return $this->builder;
    }


    /**
     * 轉布林值
     *
     * @param  string  $val  要轉的值
     *
     * @return bool
     *
     * @author   : stringer <10******56@qq.com>
     * @datetime : 2020/11/25 16:04
     */
    protected function str2Bool(string $val)
    {
        return filter_var($val, FILTER_VALIDATE_BOOLEAN);
    }

}

接下來就是定義 UserFilter 來專門處理 User 可能的篩選

<?php

namespace App\Filters;

/**
 * Class AdminOperatingLogFilter
 *
 * @package App\Filters
 */
class UserFilter extends Filter
{

    /**
     * @var string[]
     */
    protected $filters = [
        'keyword', 'regisiterTime', 'type', 'isActive',
    ];


    /**
     * 關鍵字篩選
     *
     * @param  string  $keyword
     *
     * @return \Illuminate\Database\Eloquent\Builder
     *
     * @author   : stringer <10******56@qq.com>
     * @datetime : 2020/11/24 10:35
     */
    public function keyword(string $keyword)
    {
        return $this->builder->where('nickname', 'like', "%{$keyword}%")->orWhere('username', 'like', "%{$keyword}%");
    }


    /**
     * @param  array  $rangeTime
     *
     * @return \Illuminate\Database\Eloquent\Builder
     *
     * @author   : stringer <10******56@qq.com>
     * @datetime : 2020/11/24 10:35
     */
    public function regisiterTime(array $regisiterTime)
    {
        return $this->builder->whereBetween('created_at', $regisiterTime);
    }


    /**
     * @param $isActive
     *
     * @return \Illuminate\Database\Eloquent\Builder
     *
     * @author   : stringer <10******56@qq.com>
     * @datetime : 2020/11/24 10:35
     */
    public function isActive($isActive)
    {
        return $this->builder->where('is_active', $this->str2Bool($isActive));
    }


    /**
     * @param  string  $type
     *
     * @return \Illuminate\Database\Eloquent\Builder
     *
     * @author   : stringer <10******56@qq.com>
     * @datetime : 2020/11/24 10:35
     */
    public function type(string $type)
    {
        return $this->builder->where('type', $type);
    }


}

然後我們在控制器中使用:

<?php

namespace xxx;

use App\Filters\UserFilter;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;

/**
 * Class UserController
 *
 * @package App\Http\Controllers\Admin
 */
class UserController extends Controller
{


    /**
     * @param  \Illuminate\Http\Request    $request
     * @param  \App\Filters\UserFilter     $filter
     *
     * @return \Illuminate\Http\JsonResponse
     *
     * @author   : stringer <10******56@qq.com>
     * @datetime : 2020/08/29 9:58
     */
    public function index(Request $request, UserFilter $filter)
    {
        $users = User::filter($filter)->latest()->paginate($request->input("limit"));

        return \response()->json($users);
    }

如此一來我們的程式碼就很簡潔了。把我們的所有篩選操作轉移到了 UserFilter

還有一種情況就是基本只做個關鍵字篩選,我們定義一個 KeywordFilter:

<?php

namespace App\Filters;

use Illuminate\Database\Eloquent\Builder;

/**
 * Class KeywordFilter
 *
 * @package App\Filters
 */
class KeywordFilter extends Filter
{

    /**
     * @var string[]
     */
    protected $filters = [
        'keyword',
    ];

    /**
     * @var array
     */
    protected $fields = [];


    /**
     * 設定想要搜尋的欄位
     *
     * @param  string|array  $field
     *
     * @return $this
     *
     * @author   : stringer <10******56@qq.com>
     * @datetime : 2020/11/26 14:29
     */
    public function field($field)
    {
        $field = is_array($field) ? $field : func_get_args();

        $this->fields = $field;

        return $this;
    }


    /**
     * 關鍵字篩選
     *
     * @param  string|null  $keyword  搜尋關鍵字
     *
     * @return \Illuminate\Database\Eloquent\Builder
     *
     * @author   : stringer <10******56@qq.com>
     * @datetime : 2020/11/26 14:32
     */
    protected function keyword(?string $keyword) : Builder
    {
        if (empty($this->fields)) {
            return $this->builder;
        }

        return $this->builder->where(function (Builder $builder) use ($keyword) {
            $fields = $this->fields;
            $first  = array_shift($fields);
            $builder->where($first, "like", "%{$keyword}%");
            if (!empty($fields)) {
                foreach ($fields as $field) {
                    $builder->orWhere($field, "like", "%{$keyword}%");
                }
            }
        });
    }


}

使用:

<?php

namespace App\Http\Controllers\Admin;

use App\Filters\KeywordFilter;
use App\Http\Controllers\Controller;
use App\Http\Requests\AdminUserRequest;
use App\Models\AdminUser;
use Illuminate\Http\Request;

/**
 * Class AdminUserController
 *
 * @package App\Http\Controllers\Admin
 */
class AdminUserController extends Controller
{


    /**
     * @param  \Illuminate\Http\Request    $request
     * @param  \App\Filters\KeywordFilter  $filter
     *
     * @return \Illuminate\Http\JsonResponse
     *
     * @author   : stringer <10******56@qq.com>
     * @datetime : 2020/08/29 9:58
     */
    public function index(Request $request, KeywordFilter $filter)
    {
        $adminUsers = AdminUser::filter($filter->field('username', 'nickname'))
                        ->latest()
                        ->paginate($request->input("limit"));

         return \response()->json($adminUsers);
    }

我們在 field 方法裡面傳入可以進行搜尋的匹配的欄位即可。

如此一來,如果說有很多張表需要篩選。我們就新增一個 XxxxFilter
如果一張表裡面有很多個欄位需要篩選,我們就在該 xxxFilter 下新增篩選方法即可。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章