hyvor / laravel-filterq
Laravel API 的高级筛选
Requires (Dev)
- orchestra/testbench: ^6.0
- phpstan/phpstan: ^1.5
- phpunit/phpunit: ^9
This package is auto-updated.
Last update: 2024-09-13 17:38:31 UTC
README
FilterQ 允许在 Laravel API 中进行高级筛选。例如,您可以从用户那里获取类似这样的单行输入。
name=starter&(type=image|type=video)
然后,FilterQ 可以将其转换为 Laravel Query Builder 中的 WHERE
语句,如下所示
->where(function($query) { // wrapper $query->where('name', 'starter') ->where(function($query) { $query->where('type', 'image') ->orWhere('type', 'video'); }); })
或 SQL 中的
WHERE ( name = 'starter' AND ( type = 'image' OR type = 'video') )
这就像让您的 API 消费者能够直接使用 SQL 筛选数据,但又非常安全。
FilterQ 是为 Hyvor Blogs 的数据 API 构建的。
特性
- 易于编写的单行或多行表达式。
- 逻辑运算符(
&
和|
)以及嵌套/分组(使用()
) - 安全。FilterQ 只提供对您定义的列和运算符的访问。
- 支持联合表。用户不仅可以按列筛选,还可以通过联合表进行筛选(是不是很棒?)
- 支持“类型提示”键。
- 可扩展。您可以轻松添加自己的运算符(例如:SQL
LIKE
)。
FilterQ 表达式
示例:(published_at > 1639665890 & published_at < 1639695890) | is_featured=true
一个 FilterQ 表达式 是由一个或多个以下方式连接和分组的 条件 的组合。
&
- AND|
- OR()
- 用于分组逻辑
条件有三个部分
键
运算符
值
键
通常,键是表的列名。但,它也可以是其他内容,您可以使用自定义处理程序在 Laravel 中创建 where
部分。
它应该匹配 [a-zA-Z0-9_.]+
。例如,key
、key_2
、key.child
是有效的。
运算符
默认情况下,以下运算符是支持的。
=
- 等于!=
- 不等于>
- 大于<
- 小于>=
- 大于等于<=
- 小于等于
如果您想添加更多运算符(例如:SQL LIKE
的运算符),请参阅 自定义运算符。
值
- null:
null
- 布尔值:
true
或false
- 字符串:
'hey'
或hey
- 没有引号的字符串应匹配
[a-zA-Z_][a-zA-Z0-9_-]+
。- 应以字母开头
- 可以包含字母数字字符、破折号或下划线
- 不允许空格
- 不能是
true
、false
或null
- 没有引号的字符串应匹配
- 数字:
250
、-250
或2.5
基本用法
composer require hyvor/laravel-filterq
use Hyvor\FilterQ\Facades\FilterQ; $query = FilterQ::expression('id=100|slug=hello') ->builder(Post::class) ->keys(function($keys) { $keys->add('id')->column('posts.id'); $keys->add('slug'); $keys->add('author.name') ->column('authors.name') ->join('authors', 'authors.id', '=', 'posts.author_id', 'left'); }) ->addWhere(); $posts = $query ->limit(25) ->orderBy('id', 'DESC') ->get();
FilterQ
是一个 Laravel Facade,因此您可以从您喜欢的任何方法开始。最后的方法必须是 addWhere()
。
让我们一步步来学习。
1. 设置 FilterQ 表达式和构建器
在大多数情况下,您将在 API 端点中获取一个 FilterQ 表达式的单行输入。因此,这里有一个控制器的示例。
class MyController { public function handle(Request $request) { $filter = $request->input('filter'); FilterQ::expression($expression) ->builder(Post::class) // other methods... } }
FilterQ::expression()
设置 FilterQ 表达式builder()
设置 Laravel 查询。它接受 Laravel 查询构建器、Eloquent 查询构建器、关系或模型。
2. 设置键
设置键很重要。Laravel 使用预处理语句来防止 SQL 注入。但是,预处理语句只保护“数据”,而不是列名。因此,为了防止 SQL 注入,您必须定义在 FitlerQ 表达式中允许用户定义的所有键(或列)。
在这里,我们定义了两个键:id
和 slug
。因此,用户可以在他们的 FilterQ 表达式中使用这两个键。使用其他任何键都会引发错误。
FilterQ::expression(...) ->builder(Post::class) ->keys(function ($keys) { $keys->add('id')->column('posts.id'); $keys->add('slug'); });
keys()
函数接受一个闭包,该闭包将使用一个$keys
(Hyvor\FilterQ\Keys
) 对象调用。
3. 最后,调用 addWhere()
在执行上述所有操作后,调用 addWhere()
方法。这将向您提供的构建器添加 where
语句,并返回查询构建器本身。然后您可以执行其他操作,如 limit
、orderBy
,甚至添加更多 where
语句。最后,调用 get()
获取结果。
$posts = FilterQ::expression(...) ->builder(...) ->keys(...) ->addWhere() ->limit(...) ->orderBy(...) ->get();
连接
有时,当存在键时,您想要连接外部表。让我们看看这个例子。
- 您有两个表:
posts
和authors
。 - 您有一个支持 FilterQ 表达式的
/posts
端点。 - 现在您想允许用户通过
author.name
过滤帖子。
FilterQ::expression('author.name=hyvor') ->builder(Post::class) ->keys(function($keys) { $keys->add('author.name') ->column('authors.name') ->join('authors', 'authors.id', '=', 'posts.author_id'); }) ->addWhere();
在这个例子中,在 Post::class
查询构建器上执行了两个操作。
- 添加了一个
->join()
,因为存在author.name
键。 - 添加了一个
->where()
用于authors.name
列。
需要注意的是,即使相同的键出现多次,也只会添加一个连接。
以下是上述查询在 SQL 中的样子
select * from "posts" inner join "authors" on "authors"."id" = "posts"."author_id" where ("authors"."name" = 'hyvor')
->join()
函数接受与 Laravel 的 Join 相同的参数。
左连接和右连接
上面的例子使用的是 INNER JOIN
。如果您想添加左连接或右连接,请使用第四个参数。
->join('authors', 'authors.id', '=', 'posts.author_id', 'left')
使用回调的连接
// join with WHERE $keys->add(...) ->join(function($query) { $query->join('authors', function($join) { $join->on('authors.id', '=', 'posts.author_id') ->where('authors.status', '!=', 'active'); }); }); // subquery JOINS $keys->add(...) ->join(function($query) { $query->joinSub(...); });
键运算符
可以通过键定义允许的运算符。
FilterQ::expression(...) ->builder(...) ->keys(function($keys) { // only these operators will be allowed (comma-separated string) $keys->add('id')->operators('=,>,<'); // or use an array $keys->add('slug')->operators(['=', '!=']); // exclude operators (use true as the second param) $keys->add('age')->operators('>', true); }) ->addWhere();
键值类型
可以通过键定义支持的数据类型,这是非常推荐的。
FilterQ::expression(...) ->builder(...) ->keys(function($keys) { $keys->add('id')->valueType('integer'); $keys->add('name')->valueType('string'); $keys->add('description')->valueType('string|null'); // |-seperated types $keys->add('title')->valueType(['string', 'null']); // or an array $keys->add('created_at')->valueType('date'); });
valueType
方法支持以下类型
标量
int
float
string
null
bool
特殊
numeric
- int、float 或数值字符串(使用 PHP 的 is_numeric 函数)date
- 一个有效的日期/时间字符串或整数 UNIX 时间戳。(使用 PHP 的 strtotime 函数进行解析,因此支持相对日期,如 "-7 days")
您可以使用 |
字符或发送一个数组来指定多个类型。
$keys->add('created_at')->valueType('date|null'); // or $keys->add('created_at')->valueType(['date', 'null']);
键值
可以设置键支持的值。这对于枚举列非常有用。
FilterQ::expression(...) ->builder(...) ->keys(function($keys) { // allows either published or draft $keys->add('status')->values(['published', 'draft']); // only 200 is allowed $keys->add('id')->values(200); });
自定义运算符
如果您想支持 SQL LIKE
作为运算符,您可以注册一个自定义运算符。
FilterQ::expression(...) ->builder(...) ->keys(...) ->operators(function ($operators) { $operators->add('~', 'LIKE'); });
$operators->add($filterQOperator, $sqlOperator)
$filterQOperator
应匹配以下正则表达式:[!@#$%^&*~`?]{1,2}
。简单来说,您可以使用这些特殊字符(!
@
#
$
%
^
&
*
~
?
)一次或两次作为操作符。$sqlOperator
是其对应的 SQL 操作符。
让我们看一个例子。
FilterQ::expression("title~'Hello%'") ->builder(Post::class) ->keys(function($keys) { $keys->add('title'); }) ->operators(function($operators) { $operators->add('~', 'LIKE'); }) ->addWhere();
这将创建以下 SQL 查询
select * from "posts" where ("title" LIKE 'Hello%')
移除操作符
如果您不想使用默认操作符之一,可以使用 $operators->remove($operator)
来移除它。
->operators(function($operators) { $operators->remove('>'); });
高级操作符
并非所有操作符都与 LIKE
的工作方式相似。例如,MySQL 的 MATCH AGAINST
。以下是添加此类高级操作符的方法。
重要:请正确使用
where
/orWhere
、whereRaw
/orWhere
,如下例所示。否则,逻辑可能不会按预期工作。
FilterQ::expression(...) ->builder(...) ->keys(...) ->operators(function($operators) { $operators->add('!', function($query, $whereType, $value) { /** * $query - use it to add WHERE statements * $whereType - and|or (current logical scope) * $value - value in the FilterQ expression */ // THIS IS IMPORTANT!!! $rawWhere = $whereType === 'and' ? 'whereRaw' : 'orWhereRaw'; // $query->whereRaw() $query->{$rawWhere}('MATCH (title) AGAINST (?)', [$value]); }); }) ->addWhere();
异常处理
建议使用 FilterQ 并正确处理异常。
FilterQ 可以抛出 3 个错误
Hyvor\FilterQ\Exceptions\FilterQException
Hyvor\FilterQ\Exceptions\ParserException
- 当解析 FilterQ 表达式时发生错误。Hyvor\FilterQ\Exceptions\InvalidValueException
- 当使用无效值时,该值由 键值 或 键值类型 定义
最后两个错误扩展了 FilterQException
错误,因此捕获第一个就足够了。所有错误都有一个“安全”消息(仅限英语),可以在 API 调试时显示给用户。
try { $postsBuilder = FilterQ::expressions(...) ->builder(...) ->keys(...) ->addWhere(); } catch (FilterQException $e) { dd($e->getMessage()); } $posts = $postsBuilder->get();