jpnut/eloquent-nested-filter

为 Eloquent 模型提供嵌套过滤器结构

0.1.1 2020-11-02 20:44 UTC

This package is auto-updated.

Last update: 2024-09-29 05:48:48 UTC


README

Version StyleCI GitHub Workflow Status License

此包提供了一种为 Eloquent 模型快速简洁地定义嵌套过滤器结构的方法。这使得根据用户输入以更复杂的方式过滤数据变得更加容易。

例如,给定如下定义的过滤器对象

...

class ProductFilter extends AbstractFilter
{
    public ?StringFilterObject $name;
}

我们可以使用它来过滤我们的 Product 查询

    ...

    $filter = new ProductFilter([
        'or' => [
            [
                'name' => [
                    'value' => 'foo',
                    'operator' => 'BEGINS',
                ]
            ],
            [
                'name' => [
                    'value' => 'foo',
                    'operator' => 'ENDS',
                ]
            ],
        ]
    ]);

    $query = $filter->filter(Product::query());

    ...

这将生成一个 sql 查询

SELECT * FROM `products` WHERE (`name` LIKE 'foo%' or `name` LIKE '%foo')

安装

您可以通过 composer 安装此包

composer require jpnut/eloquent-nested-filter

使用方法

要开始使用,您需要为要过滤的每个模型创建一个过滤器类。这个类应该扩展 JPNut\EloquentNestedFilter\AbstractFilter。在这个类中,您应该定义所有可过滤的属性。此包附带内置过滤器,用于主键、字符串、数字、布尔值和日期时间;尽管您可以自由创建和使用任何其他过滤器。您还可以通过包含一个引用相关模型过滤器类的属性来过滤关系。让我们看看一个示例过滤器类

// app/ProductFilter.php

namespace App;

...

class ProductFilter extends AbstractFilter
{
    public ?IDFilterObject $id = null;

    public ?StringFilterObject $name = null;

    public ?NumberFilterObject $amount = null;

    public ?BooleanFilterObject $in_stock = null;

    public ?DateFilterObject $created_at = null;

    /** @var \App\CategoryFilter[]|null */
    public ?array $category = null;
}

首先,请注意,我们通过其类型声明来定义每个属性的过滤器类型。对于数组过滤器,如 category 属性,由于 PHP 目前不支持泛型,我们必须在文档块中指定过滤器类型。每个 AbstractFilter 实例也预定义了 andor 属性,这些属性期望一个父类的数组。这些属性允许我们以预期的方式将过滤器分组在一起。

由于手动构建这些对象会很麻烦,构造函数接受一个单个关联数组,并将尝试根据类型信息转换过滤器属性。当然,如果传递了正确对象的一个实例,将使用该对象。但是,如果传递了一个数组,类将尝试构建正确的数组/对象。

让我们重新使用我们在本说明书的开头定义的示例

...

$filter = new ProductFilter([
    'or' => [
        [
            'name' => [
                'value' => 'foo',
                'operator' => 'BEGINS',
            ]
        ],
        [
            'name' => [
                'value' => 'foo',
                'operator' => 'ENDS',
            ]
        ],
    ]
]);

...

or 属性由 ProductFilter 实例的数组组成。在这种情况下,我们传递了两个关联数组 - 这些将通过将它们的值传递给 ProductFilter 的构造函数自动转换为 ProductFilter 的实例。名称过滤器以类似的方式构建。

典型请求工作流程

此库的主要用途来自用户输入。通常这意味着将 filter 关联数组作为 JSON 编码的字符串提供。然后可以通过传递解码的值构建过滤器。

...

$filter = $request->has('filter')
    ? new ProductFilter(json_decode($request->query('filter'), true))
    : null;

...

AbstractFilter 的实例公开了一个 filter 方法,它期望一个参数 - 基础 eloquent 查询。通常这将是 Product::query(),尽管当然可以限制或操纵过滤器。

...

$results = $filter->query(Product::query()->withTrashed())->get();

...

自定义过滤器对象

您可能希望添加新的过滤器对象。为此,只需创建一个新类,该类扩展 JPNut\EloquentNestedFilter\AbstractFilterObject 或实现 JPNut\EloquentNestedFilter\Contracts\FilterObject。您的类 必须 定义两个方法

abstract public function filter(string $name, Builder $query): Builder;

abstract public static function fromArray(array $properties = []): self;

filter 方法接受两个参数 - 被过滤的字段名称和查询实例,并应返回修改后的查询实例。通常,过滤器方法应处理所有有效的 Operator 值(例如,ISIS_NOT 等),如果使用无效的操作符,则抛出异常。例如

    public function filter(string $name, Builder $query): Builder
    {
        if ($this->operator->equals(Operator::IS())) {
            return $query->where($name, $this->value);
        }

        if ($this->operator->equals(Operator::IS_NOT())) {
            return $query->where($name, '<>', $this->value);
        }

        ...

        throw $this->invalidOperator($this->operator);
    }

fromArray方法接受一个参数 - 属性数组 - 应返回一个新的过滤器对象实例。当从关联数组创建过滤器对象时,会调用此方法。这是一种验证或转换过滤器值的好方法。例如,让我们看看NumberFilterObject中的fromArray函数。

    public static function fromArray(array $properties = []): self
    {
        return new static(
            is_null($properties['value'] ?? null) ? null : floatval($properties['value']),
            static::operatorFromProperties($properties)
        );
    }

在这里,我们可以看到我们允许null值,并尝试将值转换为浮点数。我们还根据提供的operator键值对获取Operator枚举实例。

验证/查询复杂度

如果不验证或限制提供的查询,用户可能会构建非常昂贵的查询,即使没有恶意意图。目前,此库不尝试验证或计算查询的复杂度。作为替代方案,可以设置最大过滤器深度和最大过滤器总数。深度是指AbstractFilter的嵌套实例:例如,每个嵌套的andor过滤器会增加深度1。过滤器总数由AbstractFitlerAbstractFilterObject的实例数确定。

回到文件顶部的示例,我们有一个深度为1(因为有1个嵌套的AbstractFilter实例,由or语句生成),总过滤器数量为3(or语句1个,每个name约束1个)。默认情况下,最大深度设置为10,允许的最大过滤器数量为100,但可以通过更改默认属性值进行修改。

...

class ProductFilter extends AbstractFilter
{
    protected ?int $max_depth = 5;

    protected ?int $max_filters = 50;
}

...

要禁用这些限制,只需将属性值设置为null。

测试

vendor/bin/phpunit

许可协议

MIT许可协议(MIT)。有关更多信息,请参阅许可文件