hyvor/laravel-filterq

Laravel API 的高级筛选

1.0.1 2022-04-11 10:21 UTC

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_.]+。例如,keykey_2key.child 是有效的。

运算符

默认情况下,以下运算符是支持的。

  • = - 等于
  • != - 不等于
  • > - 大于
  • < - 小于
  • >= - 大于等于
  • <= - 小于等于

如果您想添加更多运算符(例如:SQL LIKE 的运算符),请参阅 自定义运算符

  • null: null
  • 布尔值: truefalse
  • 字符串: 'hey'hey
    • 没有引号的字符串应匹配 [a-zA-Z_][a-zA-Z0-9_-]+
      • 应以字母开头
      • 可以包含字母数字字符、破折号或下划线
      • 不允许空格
      • 不能是 truefalsenull
  • 数字: 250-2502.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 表达式中允许用户定义的所有键(或列)。

在这里,我们定义了两个键:idslug。因此,用户可以在他们的 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 语句,并返回查询构建器本身。然后您可以执行其他操作,如 limitorderBy,甚至添加更多 where 语句。最后,调用 get() 获取结果。

$posts = FilterQ::expression(...)
    ->builder(...)
    ->keys(...)
    ->addWhere()
    ->limit(...)
    ->orderBy(...)
    ->get();

连接

有时,当存在键时,您想要连接外部表。让我们看看这个例子。

  • 您有两个表:postsauthors
  • 您有一个支持 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 查询构建器上执行了两个操作。

  1. 添加了一个 ->join(),因为存在 author.name 键。
  2. 添加了一个 ->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/orWherewhereRaw/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();