dowob/laravel-refiner

根据您选择的筛选条件,从请求数据中细化Laravel Eloquent查询。

v0.2.0 2023-08-29 17:17 UTC

This package is auto-updated.

Last update: 2024-09-29 19:28:08 UTC


README

该软件包的目标是提供简单但灵活的过滤功能,从请求参数中为Eloquent模型提供筛选。您创建一个扩展 \Dowob\Refiner\Refiner 的筛选类,并指定对该筛选器适用的筛选定义。这使得筛选器可以在整个应用程序中重复使用。

请仔细阅读 注意事项 部分,以确保此软件包适合您。这个软件包是为了满足我的需求而构建的,这意味着它可能不适合所有用例。

安装

运行以下命令以使用composer安装此软件包:

composer require dowob/laravel-refiner

如果您希望发布配置文件,则有一些最小配置选项。服务提供程序将在Laravel中自动注册。

php artisan vendor:publish --provider="Dowob\Refiner\RefinerServiceProvider" --tag="refiner-config"

可筛选模型

一个模型必须使用特质 \Dowob\Refiner\Refinable 来启用模型上的筛选。

use \Dowob\Refiner\Refinable;
use \Illuminate\Database\Eloquent\Model;

class Post extends Model {
    use Refinable;
    ...
}

筛选模型

然后您可以通过调用 refine() 来筛选查询

Post::refine()->get();

refine() 是一个作用域,它可以与模型上的任何其他调用(作用域、查询构建器方法、分页等)结合使用。

refine() 方法有两个可选参数

  1. ?\Dowob\Refiner\Refiner $refiner = null
    如果指定,这将是要用于此筛选的筛选器实例。如果没有指定,它将自动从模型名称和配置的筛选器命名空间(默认为 \App\Refiners)中确定。例如,猜测的筛选器 Post 将是 \App\Refiners\PostRefiner
  2. ?\Illuminate\Http\Request $request = null
    如果指定,它将用作请求对象以检索查询参数。否则,我们将使用当前请求来检索参数。

筛选器

一个筛选器定义了允许什么筛选(过滤器 & 排序)。这是通过在筛选器中指定 定义 来实现的。如果筛选器与 ModelNameRefiner 命名约定匹配,则自动使用筛选器,例如 PostRefiner,除非您已将筛选器实例显式传递给 refine() 方法。

筛选器通常用于特定模型,但您可能还希望为模型创建多个筛选器。例如,您可能希望使用 UserPostRefiner 在用户查看/筛选其特定帖子时,您可能还有一个适用于任何人的 PostRefiner,以便搜索任何帖子。

创建筛选器

使用 artisan 命令 php artisan make:refiner NameOfYourRefiner 生成筛选器。

定义

为了使用筛选器,您需要在 definitions() 方法中添加一个或多个定义。每个定义必须有一个 name(在 make 方法中传递),但其他所有内容都是可选的。该 name 是如何将定义与请求匹配起来的,例如,具有 email 名称的请求将触发类似 ?search[email]=value 的请求。除非您通过 column() 定义了不同的列,否则 name 将用于查询列。

您必须将定义选项加入搜索和/或排序,这可以通过调用search*方法之一或sort方法来完成。

您可以针对每个定义指定验证规则,这可以使您的定义工作更加复杂。

在筛选器中定义的定义示例

use \Dowob\Refiner\Definitions\Definition;
use \Dowob\Refiner\Enums\Like;
use \Illuminate\Support\Facades\DB;

...

public function definitions(): array
{
    return [
        // This allows exact (column = value) matching for `name` and enables sorting.
        // Note that any search that is a string will be trimmed by default.
        Definition::make('name')->search()->sort(),
        // You can disable trimming per-definition with trim(false)
        Definition::make('non-trimmed-name')->search()->trim(false),
        // This allows LIKE matching without sorting. By default, LIKE sorting will use Like::BOTH however,
        // you can override this by passing a Like::* value as the first parameter as shown in 2nd example.
        Definition::make('email')->searchLike(), // LIKE %value%
        Definition::make('email-match-after')->searchLike(Like::END), // LIKE value%
        // You can specify a different column name to use rather than the name used in the request parameter.
        // This also demonstrates support for WHERE column IN (...) type searches
        Definition::make('type')->column('account_type')->searchIn(),
        // If a pre-defined search option just doesn't cut it for you, you can specify a custom callback
        // that will be used for the search.
        Definition::make('full-name')->searchCustom(function (Builder $query, mixed $value) {
            $query->where(DB::raw('CONCAT(first_name, last_name)'), 'like', '%' . $value . '%');
        }),
        // It can also access any scopes etc. as you normally would be able to on the model
        Definition::make('model-scope')->searchCustom(function (Builder $query, mixed $value) {
            $query->aScopeOnTheModel($value);
        }),
        // You may want a search to always be applied, even if the search value isn't present in the query.
        // You can do this by specifying `alwaysRun` like so. Without the `alwaysRun`, this query would
        // not apply the `where('active', 1)` if there's no search for `active` present in the request!
        Definition::make('active')->alwaysRun()->searchCustom(function (Builder $query, mixed $value) {
            switch ($value) {
                case 'inactive':
                    $query->where('active', 0);
                    break;
                case 'all':
                    // No action, show both active & inactive
                    break;
                default:
                    // Reached either by no value being specified, or it being 'active'
                    // or anything that does not match the above
                    $query->where('active', 1);
            }
        }),
    ];
}

验证

您可以通过调用validation($rules)将验证规则添加到定义中。默认情况下,将根据定义的配置自动添加基本验证规则。

  • 除非定义设置为始终运行,否则会有nullable
  • 如果使用searchIn()搜索过滤器,则为array

验证规则使用Laravel验证系统的功能

use \Dowob\Refiner\Definitions\Definition;

// Three ways of specifying the validation rules that result in same validation
Definition::make('email')->search()->validation(['required', 'string', 'email']);
Definition::make('email')->search()->validation('required|string|email');
Definition::make('email')->search()->validation(['email' => ['required', 'string', 'email']);

如果通过searchCustom($closure)指定自定义搜索处理器,则可以解锁更强大的搜索过滤器用例。例如,如果您有一个依赖于一个定义中两个字段(startend)的日期过滤器,您可以这样做

use \Dowob\Refiner\Definitions\Definition;
use \Illuminate\Contracts\Database\Query\Builder;

// A definition using a custom search that can handle multiple query values due to its validation rules.
Definition::make('date')
    ->validation([
        'start' => [
            'required',
            'date_format:Y-m-d',
        ],
        'end' => [
            'required',
            'date_format:Y-m-d',
        ],
    ])
    ->searchCustom(function (Builder $builder, mixed $value) {
        // $value will contain at least one of `start` or `end` if they passed validation
        if (empty($value['start'])) {
            return $query->where('created_at', '=<', $value['end']);        
        }
        
        if (empty($value['end'])) {
            return $query->where('created_at', '>=', $value['start']);        
        }
        
        // Value contains both 'start' and 'end'
        $query->whereBetween('created_at', $value);
    });

注意:请注意,除非您使用searchCustomsearchIn,否则多个字段将无法工作,因为数组值会被拒绝,以防止将数组值传递到期望单个值的函数(即精确匹配搜索)。

默认排序

当请求中没有指定排序时,您可能想要将默认排序应用于查询。这可以通过在defaultSorts方法中指定一个数组来完成,该数组包含一个或多个要在没有排序的情况下应用的排序。

use \Dowob\Refiner\Enums\Sort;

...

public function defaultSorts(): array
{
    return [
        // The first value in the array must match the relevant sort-enabled definition
        // The second value is the sort direction to use as default.
        ['last_name', Sort::ASC],
        ['first_name', SORT::ASC],
    ];
}

筛选器注册表

当由于没有将筛选器传递给refine()而创建筛选器时,包会将其注册到注册表中,以便您检索(如果需要)。如果您不需要筛选器实例,则可以忽略注册表。如果您希望检查当前请求中激活的排序(即根据当前排序反转排序链接),或者检查激活的搜索及其使用的值(即用于预填充表单),或者使用query()方法检索验证后的查询参数(即用于分页的->appends()方法),则可能需要筛选器实例。

注意:筛选器是一个单例,这意味着在像Laravel Octane这样的长运行应用中,您可能需要重置其状态。

筛选器按照通过refine()注册的顺序添加到注册表中,并且当检索筛选器时,它会从注册表中删除。

您可以使用外观访问单例:\Dowob\Refiner\Facades\Registry

以下是您在使用注册表时需要了解的方法。

use \Dowob\Refiner\Facades\Registry;

// Take the refiner from the end of the registry (last in)
Registry::pop();
// Take the refiner from front of the registry (first in)
Registry::shift();

// Example
User::refine();
Post::refine();
Registry::shift(); // returns the UserRefiner
Registry::shift(); // returns the PostRefiner, as we've already shifted the UserRefiner out of registry.

两者popshift都接受两个可选参数,即Refiner的类名和Model的类名。传递这些参数之一将只过滤popshift操作以仅针对注册的筛选器执行,这些筛选器与指定的参数匹配。

注册表方法使用可选参数的示例

// Example of registry setup in this order,
// each 'we can retrieve it by' assumes no prior models are retrieved in the example.
// 1st registered, 1st UserRefiner/User registered
User::refine();
// We can retrieve it by...
// - Registry::shift()
// - or Registry::shift(UserRefiner::class);
// - or Registry::shift(model: User::class);
// 
// 2nd registered, 1st PostRefiner/Post registered
Post::refine();
// We can retrieve it by...
// - Registry::shift(PostRefiner::class)
// - or Registry::shift(model: Post::class);
//
// 3rd registered, 2nd UserRefiner/User registered
User::fine();
// We can retrieve it by...
// - Registry::pop(UserRefiner::class)
// - or Registry::pop(model: User::class);
// 
// 4th registered, 2nd PostRefiner/Post registered
Post::fine();
// We can retrieve it by...
// - Registry::pop()
// - or Registry::pop(PostRefiner::class)
// - or Registry::pop(model: Post::class);

注意事项

  1. 此包不支持AND以外的其他筛选器组合,除非它们位于自定义搜索过滤器中(但这将与任何其他活动筛选器进行AND运算)。
  2. 虽然支持单个请求中的多个筛选器,但如果定义具有冲突的名称,则可能得到意外的结果(因为如果数据验证符合其定义,则两个筛选器都会使用它们)。
    1. 筛选器中的定义名称必须是唯一的,这可能在以后的版本中强制执行。
  3. 如果在单个定义中验证多个字段,则必须将定义名称传递给getSearchValue($name),这将返回一个数组,包含在该定义中通过验证的任何字段的值。您不能在getSearchValue($name)中通过名称调用字段,但可以这样做,如getSearchValue($name)[$field] ?? null

待办事项

  • 筛选
    • 通过基本关系whereHas进行搜索,以减少需要使用实现相同结果的searchCustom回调的次数
      • 为提高性能而进行的相同关系查询的潜在分组