lmc/api-filter

API查询参数的过滤器解析器/构建器。

3.0.0 2021-05-06 07:37 UTC

This package is auto-updated.

Last update: 2024-09-06 14:54:29 UTC


README

Latest Stable Version Build Status Coverage Status

API查询参数的过滤器解析器/构建器。

它只是一个过滤器解析器/构建器,不是一个业务逻辑的地方,因此如果您想对过滤器更加严格,应该由您的类包装。同样,如果您想为每个实体/表设置不同的设置,也应该通过围绕此库的特定包装器来完成。

目录

安装

composer require lmc/api-filter

用法

例如,让我们从以下请求中获取查询参数

GET http://host/endpoint/?field=value

初始化

// in DI container/factory
$apiFilter = new ApiFilter();
$apiFilter->registerApplicator(...);  // optional, when you want to use non-standard implementation

// in service/controller/...
$filters = $apiFilter->parseFilters($request->query->all());

// [
//     0 => Lmc\ApiFilter\Filter\FilterWithOperator {
//         private $title    => 'eq'
//         private $operator => '='
//         private $column   => 'field'
//         private $value    => Lmc\ApiFilter\Entity\Value {
//             private $value => 'value'
//         }
//     }
// ]

使用Doctrine Query Builder Applicator

  • 需要安装 doctrine/orm
  • 应用过滤器使用 克隆 QueryBuilder -> 原始 QueryBuilder未更改

示例

// in EntityRepository/Model
$queryBuilder = $this->createQueryBuilder('alias');
$queryBuilder = $apiFilter->applyFilters($filters, $queryBuilder);

// or one by one
foreach ($filters as $filter) {
    $queryBuilder = $apiFilter->applyFilter($filter, $queryBuilder);
}

// get prepared values for applied filters
$preparedValues = $apiFilter->getPreparedValues($filters, $queryBuilder); // ['field_eq' => 'value']

// get query
$queryBuilder
    ->setParameters($preparedValues)
    ->getQuery();

更简短的示例 (与☝相同)

// in EntityRepository/Model
$queryBuilder = $this->createQueryBuilder('alias');

$apiFilter
    ->applyFilters($filters, $queryBuilder)                                     // query builder with applied filters
    ->setParameters($apiFilter->getPreparedValues($filters, $queryBuilder)) // ['field_eq' => 'value']
    ->getQuery();

使用 SQL Applicator

  • ❗这只是一个 原始实现,应谨慎使用❗
  • 它仍然可以在没有 ORDER BYGROUP BYSQL 的简单情况下使用,因为它只是将过滤器作为 WHERE 条件添加

SQL Applicator 必须显式注册

// in DI container
$apiFilter->registerApplicator(new SqlApplicator(), Priority::MEDIUM);

示例

// in Model/EntityRepository
$sql = 'SELECT * FROM table';
$sql = $apiFilter->applyFilters($filters, $sql); // "SELECT * FROM table WHERE 1 AND field = :field_eq"

// or one by one
foreach ($filters as $filter) {
    $sql = $apiFilter->applyFilter($filter, $sql);
}

// get prepared values for applied filters
$preparedValues = $apiFilter->getPreparedValues($filters, $sql); // ['field_eq' => 'value']

// execute query
$stmt = $connection->prepare($sql);
$stmt->execute($preparedValues);

更简短的示例 (与☝相同)

// in EntityRepository/Model
$sql = 'SELECT * FROM table';
$stmt = $connection->prepare($apiFilter->applyFilters($filters, $sql)); // SELECT * FROM table WHERE 1 AND field = :field_eq 
$stmt->execute($apiFilter->getPreparedValues($filters, $sql));      // ['field_eq' => 'value']

支持的过滤器

等于 - EQ (=)

GET http://host/endpoint/?field[eq]=value
GET http://host/endpoint/?field=value

两个示例 ☝ 是相同的

不等于 - NEQ (!=)

GET http://host/endpoint/?field[neq]=value

大于 - GT (>)

GET http://host/endpoint/?field[gt]=value

大于等于 - GTE (>=)

GET http://host/endpoint/?field[gte]=value

小于 - LT (<)

GET http://host/endpoint/?field[lt]=value

小于等于 - LTE (<=)

GET http://host/endpoint/?field[lte]=value

IN

GET http://host/endpoint/?type[in][]=one&type[in][]=two
  • IN 过滤器不允许使用 Tuples

函数

GET http://host/endpoint?fullName=(Jon,Snow)
  • 您可以在 这里 看到使用 functions 的更多选项和可能性

过滤器中的 Tuples

元组

  • 在过滤器中非常重要,如果您有一些必须一起发送的值
  • 由两个或多个值组成(Tuple 中一个值的元组只是一个值
  • 项目 必须( ) 中,并用 , 分隔
    • Tuple 中的 array 必须[ ] 中,并用 ; 分隔项目
  • 建议不要在值之间使用 空格,因为 URL 的特定行为
  • 有关 Tuples 的更多信息,请参阅 https://github.com/MortalFlesh/MFCollectionsPHP#immutabletuple

包含 Tuple 的列

Tuple 声明的列的行为与单个值相同,但其值也必须是 Tuple。列可以为每个值包含过滤器规范。

  • 默认过滤器是单个值的 EQ,以及值数组的 IN(在 Tuple 中)

包含 Tuple 的值

元组中的值必须与列数相同。值可以包含对元组中所有值的筛选规格。

注意:筛选规格不能同时出现在列和值中。

用法

GET http://host/endpoint/?(first,second)[eq]=(one,two) 

☝ 表示您有两个列 firstsecond,它们必须一起发送。列 first 必须等于值 "one",列 second 必须等于值 "two"

示例

❗为了示例的简洁性,它们显示在SQL 应用程序上,该应用程序不是自动注册的❗

IN + EQ 筛选

GET http://host/person/?type[in][]=student&type[in][]=admin&name=Tom
$parameters = $request->query->all();
// [
//     "type" => [
//         "in" => [
//             0 => "student"
//             1 => "admin"
//         ]
//     ],
//     "name" => "Tom"
// ]

$filters = $apiFilter->parseFilters($parameters);
$sql = 'SELECT * FROM person';

foreach ($filters as $filter) {
    $sql = $apiFilter->applyFilter($filter, $sql);
    
    // 0. SELECT * FROM person WHERE 1 AND type IN (:type_in_0, :type_in_1) 
    // 1. SELECT * FROM person WHERE 1 AND type IN (:type_in_0, :type_in_1) AND name = :name_eq 
}

$preparedValues = $apiFilter->getPreparedValues($filters, $sql);
// [
//     'type_in_0' => 'student',
//     'type_in_1' => 'admin',
//     'name_eq'   => 'Tom',
// ]

GT + LT 筛选(介于两者之间

GET http://host/person/?age[gt]=18&age[lt]=30
$parameters = $request->query->all();
// [
//     "age" => [
//         "gt" => 18
//         "lt" => 30
//     ],
// ]

$filters = $apiFilter->parseFilters($parameters);
$sql = 'SELECT * FROM person';

$sql = $apiFilter->applyFilters($filters, $sql); // SELECT * FROM person WHERE 1 AND age > :age_gt AND age < :age_lt
$preparedValues = $apiFilter->getPreparedValues($filters, $sql); // ['age_gt' => 18, 'age_lt' => 30]

EQ元组

GET http://host/person/?(firstname,surname)=(John,Snow)
$parameters = $request->query->all(); // ["(firstname,surname)" => "(John,Snow)"]

$sql = 'SELECT * FROM person';
$filters = $apiFilter->parseFilters($parameters);
// [
//     0 => Lmc\ApiFilter\Filter\FilterWithOperator {
//         private $title    => "eq"
//         private $operator => "="
//         private $column   => "firstname"
//         private $value    => Lmc\ApiFilter\Entity\Value {
//             private $value => "John"
//         }
//     },
//     1 => Lmc\ApiFilter\Filter\FilterWithOperator {
//         private $title    => "eq"
//         private $operator => "="
//         private $column   => "surname"
//         private $value    => Lmc\ApiFilter\Entity\Value {
//             private $value => "Snow"
//         }
//     }
// ]

$sql = $apiFilter->applyFilters($filters, $sql); // SELECT * FROM person WHERE 1 AND firstname = :firstname_eq AND surname = :surname_eq
$preparedValues = $apiFilter->getPreparedValues($filters, $sql); // ['firstname_eq' => 'John', 'surname_eq' => 'Snow']

更多示例

等于(隐式和显式

GET http://host/person/?fullName=Jon Snow
GET http://host/person/?fullName[eq]=Jon Snow

结果

-   column: fullName
    filters: eq
    value: Jon Snow

多个筛选(隐式和显式

通过单个值

GET http://host/person/?firstName=Jon&surname=Snow
GET http://host/person/?firstName[eq]=Jon&surname[eq]=Snow

通过元组

GET http://host/person/?(firstName,surname)=(Jon,Snow)
GET http://host/person/?(firstName,surname)[eq]=(Jon,Snow)
GET http://host/person/?(firstName[eq],surname[eq])=(Jon,Snow)

结果

-   column: firstName
    filters: eq
    value: Jon

-   column: surname
    filters: eq
    value: Snow

多个筛选

您可以将所有类型的筛选(元组、显式、隐式)混合使用。

通过通用筛选找到完美的妻子

通过单个值

GET http://host/person/?age[gte]=18&age[lt]=30&category[in][]=serious&category[in][]=marriage&sense-of-humor=true

通过元组

GET http://host/person/?(age[gte],age[lt],category,sense-of-humor)=(18,30,[serious;marriage],true)

结果

-   column: age
    filters: gte
    value: 18

-   column: age
    filters: lt
    value: 30

-   column: category
    filters: in
    value: [ serious, marriage ]

-   column: sense-of-humor
    filters: eq
    value: true
通过通用筛选找到想看电影的

通过单个值

GET http://host/movie/?year[gte]=2018&rating[gte]=80&genre[in][]=action&genre[in][]=fantasy

通过元组

GET http://host/movie/?(year[gte],rating[gte],genre)=(2018,80,[action;fantasy])

结果

-   column: year
    filters: gte
    value: 2018

-   column: rating
    filters: gte
    value: 80

-   column: genre
    filters: in
    value: [ action, fantasy ]

过滤器中的函数

使用函数可以处理所有可能的问题,这些问题可能只用简单的筛选(如 eq 等)会有问题。

fullName 函数示例

让我们看看如何使用函数以及需要做什么。我们将在示例中展示。

预期的API

GET http://host/endpoint?fullName=(Jon,Snow)

☝️ 上面的示例显示了我们要提供给消费者的内容。它既简单又明确。

它甚至可能隐藏一些内部差异,例如,对于简单筛选,数据库列必须与筛选中的字段同名,但对于函数,我们可以更改它。

假设在数据库中我们有一些类似的内容

type Person = {
    first_name: string
    lastname: string
}

初始化

首先,您必须定义您想要使用的函数。

// in DI container/factory
$apiFilter = new ApiFilter();

$apiFilter->declareFunction(
    'fullName',
    [
        new ParameterDefinition('firstName', 'eq', 'first_name'),   // parameter name and field name are different, so we need to define it
        'lastname`,              // parameter name and field name are the same and we use the implicit `eq` filter, so it is defined simply 
    ]
);

declareFunction 方法将根据参数创建具有筛选的函数。
还有一个 registerFunction 方法,它允许您传递任何您想要的函数。这可能在使用根本不需要筛选功能或有一些自定义存储等情况时很有用。

解析和应用筛选

现在当请求带有 ?fullName=(Jon,Snow) 时,ApiFilter 可以将其解析为

// in service/controller/...
$sql = 'SELECT * FROM person';

$filters = $apiFilter->parseFilters($request->query->all());
// [
//      0 => Lmc\ApiFilter\Filter\FilterFunction {
//        private $title  => 'function'
//        private $column => 'fullName'
//        private $value  => Lmc\ApiFilter\Entity\Value {
//          private $value => Closure
//        }
//      },
//
//      1 => Lmc\ApiFilter\Filter\FunctionParameter {
//        private $title => 'function_parameter'
//        private $column => 'firstName'
//        private $value => Lmc\ApiFilter\Entity\Value {
//          private $value => 'Jon'
//        }
//      },
//
//      2 => Lmc\ApiFilter\Filter\FunctionParameter {
//        private $title => 'function_parameter'
//        private $column => 'lastname'
//        private $value => Lmc\ApiFilter\Entity\Value {
//          private $value => 'Snow'
//        }
//      }
// ]

$appliedSql = $apiFilter->applyFilters($filters, $sql);
// SELECT * FROM person WHERE first_name = :firstName_function_parameter AND lastname = :lastname_function_parameter

$preparedValues = $apiFilter->getPreparedValues($filters, $sql);
// [
//      'firstName_function_parameter' => 'Jon',
//      'lastname_function_parameter' => 'Snow',
// ]

支持的功能使用

以下所有示例的结果相同。我们有很多选项,因此我们可以允许尽可能多的不同消费者。

### Explicit function call
GET http://host/endpoint?fullName=(Jon,Snow)

### Explicit function call with values 
GET http://host/endpoint?function=fullName&firstName=Jon&lastname=Snow

### Implicit function call by values
GET http://host/endpoint?firstName=Jon&lastname=Snow

### Explicit function call by tuple 
GET http://host/endpoint?(function,firstName,surname)=(fullName, Jon, Snow)

### Implicit function call by tuple
GET http://host/endpoint?(firstName,surname)=(Jon, Snow)

### Explicit function call by filter parameter 
GET http://host/endpoint?filter[]=(fullName,Jon,Snow)

功能参数定义

声明注册 函数,您必须定义其参数。有许多方法/需求来完成此操作。

定义为字符串

这是最简单的方法。您只需定义一个名称。

这意味着

  • 您想要 eq 筛选(IN 用于数组)以及列名和参数名相同
  • 此参数的值是必需的
$apiFilter->declareFunction('fullName', ['firstName', 'surname']);

定义为数组

这允许您为参数传递更多选项。

只有一个项目

如果您只通过给出唯一的项目来声明它,它就与上面的字符串定义相同。

$apiFilter->declareFunction('fullName', [['firstName'], ['surname']]);
多个项目

这意味着

  • firstName 参数使用 eq 筛选,在存储中有 first_name 列,并且是必需的
  • surname 参数使用 eq 筛选,在存储中有 lastname 列,其值为 Snow它将始终使用,无法用其他值覆盖
$apiFilter->declareFunction('fullName', [
    ['firstName', 'eq', 'first_name'],
    ['surname', 'eq', 'lastname', 'Snow']
]);

定义为对象

这允许您传递与数组相同的选项,但以显式定义的对象形式。 (它还具有一些特殊的构造方法来简化创建

$apiFilter->declareFunction('fullName', [
    new ParameterDefinition('firstName', 'eq', 'first_name'),
    new ParameterDefinition('surname', 'eq', 'lastname, new Value('Snow'))
]);

组合

可以将所有选项组合起来以最好地适应参数。

声明
$apiFilter->declareFunction('fullNameGrownMan', [
    ['firstName', 'eq', 'first_name'],
    'surname',
    ['age', 'gte', 'age', 18],
    ParameterDefinition::equalToDefaultValue('gender', new Value('male')),
]);
用法
GET http://endpoint/host?fullNameGrownMan=(Jon,Snow)

注册和执行函数

下面的示例只是为了明确说明,您可能永远不会允许执行这样的 SQL 查询。

在PHP中的使用

// in DI container/factory
$apiFilter = new ApiFilter();

$apiFilter->registerFunction(
    'sql',
    ['query'],
    function (\PDO $client, FunctionParameter $query): \PDOStatement {
        return $client->query($query->getValue()->getValue());
    }
);

// in service/controller/...
$statement = $apiFilter->executeFunction('sql', $queryParameters, $client);    // \PDOStatement

$statement->execute();
// fetch result, etc...

API 的使用

以下所有示例的结果相同。我们有很多选项,因此我们可以允许尽可能多的不同消费者。

### Explicit function call
GET http://endpoint/host?sql=SELECT * FROM person

### Explicit function call with values
GET http://host/endpoint?function=sql&query=SELECT * FROM person

### Implicit function call by values
GET http://host/endpoint?query=SELECT * FROM person

### Explicit function call by tuple
GET http://host/endpoint?(function,query)=(sql, SELECT * FROM person)

### Explicit function call by filter parameter
GET http://host/endpoint?filter[]=(sql, SELECT * FROM person)

异常和错误处理

已知的 ApiFilter 实现中发生的异常发生 Lmc\ApiFilter\Exception\ApiFilterExceptionInterface。异常树是

请注意,如果您将自定义的应用器注册到ApiFilter(通过$apiFilter->registerApplicator()),它可能会抛出其他异常,这些异常可能未实现ApiFilterExceptionInterface

开发

安装

composer install

测试

composer all

待办事项

  • defineAllowed:(这应该在DI级别上
    • 字段(列)
    • 过滤器
  • 添加更多示例
    • 每个实体/表的不同配置