simexis / laravel-filterable
为 Laravel 模型添加查询过滤功能的特性
Requires
- php: >=7.2
- illuminate/support: ^6.0|^7.0|^8.0|^9.0
This package is auto-updated.
Last update: 2024-09-12 14:33:09 UTC
README
为 Laravel 模型提供可组合查询的特质。
预期用法是提供一种简单的方式来声明模型字段的可过滤性,并允许过滤查询以易于表示的形式(例如 JSON 对象)来表示。
Simexis\Filterable\FilterableTrait 为模型添加了一个 filter() 方法,该方法与 Laravel 查询构建器一起工作。filter() 方法接受一个数组作为单个参数,该数组包含 [字段 => 值] 对,定义了正在进行的搜索查询。
安装
composer require simexis/laravel-filterable
用法
基本用法是将特质添加到模型类中并配置可过滤字段。
use Simexis\Filterable\Filterable;
class User extends Model
{
use \Simexis\Filterable\FilterableTrait;
protected $filterable = [
'name' => Filterable::String,
'email' => Filterable::String
];
}
$filter = [
'name' => 'John'
];
User::filter($filter)->toSql();
// select * from users where name = ?
$filter = [
'name_LIKE' => 'Jo%n',
'email' => 'john@example.com'
];
User::filter($filter)->toSql();
// select * from users where name like ? and email = ?
$filterable 属性定义了可用于过滤查询的字段。其值是一个包含过滤器可能使用的可能变体的 过滤规则 的数组。
内置规则包括
- EQ - 等于
- LIKE - SQL LIKE
- MATCH - 通配符模式匹配
- MIN - 大于等于
- MAX - 小于等于
- GT - 大于
- LT - 小于
- RE - 正则表达式
- FT - 全文搜索(见以下说明)
- IN - 包含在列表中
- NULL - NULL 比较
提供一组标准规则
- 字符串 = [EQ, LIKE, MATCH]
- 数字 = [EQ, MIN, MAX, LT, GT]
- 枚举 = [EQ, IN]
- 日期 = [EQ, MIN, MAX, LT, GT]
- 布尔值 = [EQ]
模型的可过滤定义设置了每个字段可用的规则。列表中的第一个规则是该字段的默认规则。其他规则必须在查询字段名称中添加规则名称作为后缀。
在以下定义中,'name' 字段可以使用任何 String 规则:EQ、LIKE、MATCH。
User::$filterable = [
'name' => Filterable::String
]
因此,查询可以使用
$filter = [
'name_MATCH' => 'John'
];
User::filter($filter);
运行的 SQL 将匹配任何名称为不区分大小写的匹配包含 'John' 的用户,例如 'Little john'、'Johnathon'、'JOHN'。
模型的可过滤定义还可以通过在类上重载 getFilterable() 方法动态返回。
class User extends Model
{
use \Simexis\Filterable\FilterableTrait;
public function getFilterable () {
return [
'name' => Filterable::String,
'email' => Filterable::String
];
}
}
自定义规则
类可以为字段提供自定义规则。
class User extends Model
{
use \Simexis\Filterable\FilterableTrait;
protected $filterable = [
'keyword' => 'Keyword'
];
public function scopeFilterKeyword($query, $field, $arg) {
$query->where(function ($q) use ($arg) {
$q->where('name', $arg);
$q->orWhere('email', $arg);
$q->orWhere('phone', $arg);
});
return $query;
}
}
自定义规则可以与内置规则一起列出在 $filterable 定义中,并按相同的方式工作。$filterable 中的第一个规则是该字段的默认规则。如果不是第一个规则,它必须在查询字段名称中添加规则名称作为后缀。
规则名称被转换为 'ucfirst' 并附加到 'scopeFilter'。
否定规则
可以使用 'NOT' 修饰符否定默认规则,或者对于其他规则,可以在规则修饰符前缀 'NOT_'。
// anyone but John
$filter = [
'name_NOT' => 'John'
];
User::filter($filter)->toSql();
// select * from users where name != ?
// any status except 'active' or 'expired'
$filter = [
'status_NOT_IN' => ['active', 'expired']
];
User::filter($filter)->toSql();
// select * from users where status not in (?, ?)
注意:比较规则(MIN、MAX、LT、GT)没有否定形式。
自定义规则可以通过定义相应的范围函数来实现否定版本,该函数具有类似的名称,但在规则名称之前添加了单词 'Not'。
class User extends Model
{
...
public function scopeFilterNotKeyword($query, $field, $arg) {
return $query
->where('name', '!=', $arg)
->where('email', '!=', $arg)
->where('phone', '!=', $arg);
}
}
使用 AND、OR、NOT、和 NOR 进行逻辑组合
通过将过滤结构为嵌套查询,可以创建更复杂的查询。
$filter = [
'OR' => [
[
'name' => 'John'
],
[
'name' => 'Alice'
]
]
];
User::filter($filter)->toSql();
// select * from users where ((name = ?) or (name = ?))
'AND'、'OR'、'NOT' 和 'NOR' 嵌套操作符分别接受要应用的一组嵌套过滤。
关系
可以使用关系来过滤相关模型。
class Post extends Model
{
use \Simexis\Filterable\FilterableTrait;
public function getFilterable () {
return [
'comment' => $this->comments()
];
}
public function comments() {
return $this->hasMany(Comment::class);
}
}
class Comment extends Model
{
use \Simexis\Filterable\FilterableTrait;
public function getFilterable () {
return [
'created_at' => Filterable::Date
];
}
public function post() {
return $this->belongsTo(Post::class);
}
}
# Get all posts that have a comment in June 2017
$filter = [
'comment' => [
'created_at_MIN' => '2017-06-01',
'cerated_at_MAX' => '2017-07-01'
]
];
Post::filter($filter)->toSql()
// select * from "posts"
// inner join (
// select distinct "comments"."post_id" from "comments"
// where "created_at" >= ? and "created_at" <= ?
// ) as "comments_1" on "posts"."id" = "comments_1"."post_id"
链式过滤
在链式过滤时,会进行深度合并。
class Post extends Model
{
use \Simexis\Filterable\FilterableTrait;
public function getFilterable () {
return [
'comment' => $this->comments()
];
}
public function comments() {
return $this->hasMany(Comment::class);
}
}
class User extends Model
{
use \Simexis\Filterable\FilterableTrait;
protected $filterable = [
'id' => Filterable::Integer
];
}
class Comment extends Model
{
use \Simexis\Filterable\FilterableTrait;
public function getFilterable () {
return [
'created_at' => Filterable::Date,
'author' => $this->author()
];
}
public function post() {
return $this->belongsTo(Post::class);
}
public function author() {
return $this->belongsTo(User::class);
}
}
$filter1 = [
'comment' => [
'created_at_MIN' => '2017-06-01',
'created_at_MAX' => '2017-07-01'
]
];
$filter2 = [
'comment' => [
'author' => [
'id' => 1
]
]
];
Post::filter($filter1)->filter($filter2)->toSql()
// select * from "posts"
// inner join (
// select distinct "comments"."post_id"
// from "comments"
// inner join (
// select distinct "users"."id" as "author_id" from "users"
// where "id" = ?
// ) as "authors_1" on "comments"."author_id" = "authors_1"."author_id"
// where "created_at" >= ? and "created_at" <= ?
// ) as "comments_1" on "posts"."id" = "comments_1"."post_id"
要避免深度合并,可以使用 filterApply()
方法。
Post::filter($filter1)->filterApply()->filter($filter2)->toSql()
// select * from "posts"
// inner join (
// select distinct "comments"."post_id" from "comments"
// where "created_at" >= ? and "created_at" <= ?
// ) as "comments_1" on "posts"."id" = "comments_1"."post_id"
// inner join (
// select distinct "comments"."post_id" from "comments"
// inner join (
// select distinct "users"."id" as "author_id" from "users"
// where "id" = ?
// ) as "authors_1" on "comments"."author_id" = "authors_1"."author_id"
// ) as "comments_2" on "posts"."id" = "comments_2"."post_id"
全文搜索
Filterable::FT
规则提供了在 PostgreSQL 中使用 tsearch 进行全文搜索的基本形式。
为了使用全文规则,应用程序必须提供一个包含搜索数据的表。默认情况下,表的名称以模型名称为基础,后缀为 _filterable
,并且与模型的表使用相同的唯一键进行一对一映射。tsearch 矢量数据存储在以字段名称为基础,后缀为 _vector
的列中。可以自定义表和矢量字段名称,通过为 filterableFtTable 和 filterableFtVector 属性提供值来实现。
-- Content model table
create table posts (id int primary key, body text);
-- Full text search data
create table posts_filterable (id int references posts (id), body_vector tsvector);
应用程序必须确保矢量字段得到适当的更新(通常通过定义触发器函数)。
class Post
{
use \Simexis\Filterable\FilterableTrait;
protected $filterable = [
'body' => Filterable::FT
];
}
$filter = [
'body' => 'fat cats'
];
Post::filter($filter)->orderBy('body_rank', 'desc')->toSql();
// select * from "posts" inner join (
// select "id", ts_rank("body_vector", query) as "body_rank"
// from "posts_filterable"
// cross join plainto_tsquery(?) query
// where "body_vector" @@ "query"
// ) as body_1 on "posts"."id" = "body_1"."id"
// order by "body_rank" desc
模型可以提供自定义的搜索表、外键和字段名称的值。
class Post
{
use \Simexis\Filterable\FilterableTrait;
protected $filterable = [
'body' => Filterable::FT
];
protected $filterableFtTable = 'search';
protected $filterableFtKeyName = 'post_id';
protected $filterableFtVector = 'data';
}
这些自定义值也可以通过提供“获取”函数动态评估。
class Post
{
use \Simexis\Filterable\FilterableTrait;
protected $filterable = [
'body' => Filterable::FT
];
public function getFilterableFtTable ($field) {
return 'search';
};
public function getFilterableFtKeyName ($field) {
return 'post_id';
}
public function getFilterableFtVector ($field) {
return $field;
}
}