dowob / laravel-refiner
根据您选择的筛选条件,从请求数据中细化Laravel Eloquent查询。
Requires
- php: ^8.0
- illuminate/database: ^9.0|^10.0
- illuminate/http: ^9.0|^10.0
- illuminate/support: ^9.0|^10.0
- illuminate/validation: ^9.0|^10.0
- spatie/laravel-package-tools: ^1.12
Requires (Dev)
- laravel/framework: ^9.0|^10.0
- nunomaduro/larastan: ^2.1
- orchestra/testbench: ^7.0|^8.0
- pestphp/pest: ^1.20
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()
方法有两个可选参数
?\Dowob\Refiner\Refiner $refiner = null
如果指定,这将是要用于此筛选的筛选器实例。如果没有指定,它将自动从模型名称和配置的筛选器命名空间(默认为\App\Refiners
)中确定。例如,猜测的筛选器Post
将是\App\Refiners\PostRefiner
。?\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)
指定自定义搜索处理器,则可以解锁更强大的搜索过滤器用例。例如,如果您有一个依赖于一个定义中两个字段(start
和end
)的日期过滤器,您可以这样做
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); });
注意:请注意,除非您使用
searchCustom
或searchIn
,否则多个字段将无法工作,因为数组值会被拒绝,以防止将数组值传递到期望单个值的函数(即精确匹配搜索)。
默认排序
当请求中没有指定排序时,您可能想要将默认排序应用于查询。这可以通过在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.
两者pop
和shift
都接受两个可选参数,即Refiner
的类名和Model
的类名。传递这些参数之一将只过滤pop
或shift
操作以仅针对注册的筛选器执行,这些筛选器与指定的参数匹配。
注册表方法使用可选参数的示例
// 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);
注意事项
- 此包不支持AND以外的其他筛选器组合,除非它们位于自定义搜索过滤器中(但这将与任何其他活动筛选器进行AND运算)。
- 虽然支持单个请求中的多个筛选器,但如果定义具有冲突的名称,则可能得到意外的结果(因为如果数据验证符合其定义,则两个筛选器都会使用它们)。
- 筛选器中的定义名称必须是唯一的,这可能在以后的版本中强制执行。
- 如果在单个定义中验证多个字段,则必须将定义名称传递给
getSearchValue($name)
,这将返回一个数组,包含在该定义中通过验证的任何字段的值。您不能在getSearchValue($name)
中通过名称调用字段,但可以这样做,如getSearchValue($name)[$field] ?? null
待办事项
- 筛选
- 通过基本关系
whereHas
进行搜索,以减少需要使用实现相同结果的searchCustom
回调的次数- 为提高性能而进行的相同关系查询的潜在分组
- 通过基本关系