peckrob/search-parser

一个将自由形式的查询转换为中间对象的解析器,该对象可以转换为查询多个后端(SQL、ElasticSearch等)。

v0.2 2020-05-14 20:33 UTC

This package is auto-updated.

Last update: 2024-10-01 00:06:30 UTC


README

SearchParser是一个将自由形式的查询转换为中间对象的解析器,然后可以将其转换为查询多个后端(SQL、ElasticSearch等)。它包括SQL(使用PDO)和Laravel Eloquent ORM的翻译器。它支持在许多网站上常见的事实语言搜索。

例如,考虑以下查询

from:foo@example.com "bar baz" !meef date:2018/01/01-2018/08/01 #hashtag

使用SearchParser,它被标记化为一个包含一系列表示搜索查询每个逻辑组件的SearchQueryComponentSearchQuery对象。

$q = new \peckrob\SearchParser\SearchParser();
$x = $q->parse($query);
print_r($x);

返回

peckrob\SearchParser\SearchQuery Object
(
    [position:peckrob\SearchParser\SearchQuery:private] => 0
    [data:protected] => Array
        (
            [0] => peckrob\SearchParser\SearchQueryComponent Object
                (
                    [type] => field
                    [field] => from
                    [value] => foo@example.com
                    [firstRangeValue] =>
                    [secondRangeValue] =>
                    [negate] =>
                )

            [1] => peckrob\SearchParser\SearchQueryComponent Object
                (
                    [type] => text
                    [field] =>
                    [value] => bar baz
                    [firstRangeValue] =>
                    [secondRangeValue] =>
                    [negate] =>
                )

            [2] => peckrob\SearchParser\SearchQueryComponent Object
                (
                    [type] => text
                    [field] =>
                    [value] => meef
                    [firstRangeValue] =>
                    [secondRangeValue] =>
                    [negate] => 1
                )

            [3] => peckrob\SearchParser\SearchQueryComponent Object
                (
                    [type] => range
                    [field] => date
                    [value] =>
                    [firstRangeValue] => 2018/01/01
                    [secondRangeValue] => 2018/08/01
                    [negate] =>
                )

            [4] => peckrob\SearchParser\SearchQueryComponent Object
                (
                    [type] => text
                    [field] =>
                    [value] => #hashtag
                    [firstRangeValue] =>
                    [secondRangeValue] =>
                    [negate] =>
                )

        )

)

安装

使用composer安装

composer require peckrob/search-parser

没有外部依赖。只在PHP 7.2+上进行了测试,但可能也适用于PHP5。但你不应该使用PHP5。 :)

解析

要将字符串解析为组件令牌,创建一个SearchParser实例,并调用其上的parse()

$q = new \peckrob\SearchParser\SearchParser();
$x = $q->parse($query);

这将返回一个包含一系列SearchQueryComponentsSearchQuery对象。SearchQuery对象是可迭代的,你可以使用foreach循环遍历它。

定义自定义解析器

内置解析器可以很好地解析上述字符串并支持一组基本功能。但是,如果您需要扩展解析器以解析附加数据,可以轻松做到。您可以创建一个实现\peckrob\SearchParser\Parsers\Parser接口的类,并实现返回SearchQueryComponent对象的parsePart()方法。这将在解析器生成的SearchQuery对象返回之前添加。

然后,只需通过调用addParser()将自定义解析器添加到SearchParser

$custom = new \peckrob\SearchParser\Parsers\Hashtag();
$q = new \peckrob\SearchParser\SearchParser();
$q->addParser($custom);
$q->parse($query);

返回

...

            [4] => peckrob\SearchParser\SearchQueryComponent Object
                (
                    [type] => hashtag
                    [field] =>
                    [value] => hashtag
                    [firstRangeValue] =>
                    [secondRangeValue] =>
                    [negate] =>
                )

您可以在Parsers中的Hashtag解析器中查看示例。当然,您需要提供一个匹配的自定义转换来处理您的新自定义组件类型(见下文)。请注意,解析器不会“传递”。如果您的解析器处理了一个部分,它将转到下一个部分。

转换

在包中包含了一些示例转换。这些将parse()SearchQuery输出转换为适合查询后端格式的格式。包括适合直接查询Laravel Eloquent模型对象的SQL后端和Eloquent后端。

要使用转换,创建一个Transformer实例,传入根据转换器可选的默认字段和上下文对象。

$pdo = new PDO("sqlite:/tmp/foo.sql");

$transform = new \peckrob\SearchParser\Transforms\SQL\SQL("default_field", $pdo);
$where = $transform->transform($x);

返回

`from` = 'foo@example.com' and `default_field` = 'bar baz' and `default_field` != 'meef' and (`date` between '2018/01/01' and '2018/08/01') and `default_field` = '#hashtag'

使用Laravel/Lumen

SearchParser原生支持Laravel/Lumen Eloquent ORM查询。您可以使用Eloquent转换。

$user = User::take(100);
$transform = new \peckrob\SearchParser\Transforms\Eloquent\Eloquent("default_field", $user);
$user = $transform->transform($x);

这将返回带有所有where()等的$user对象,准备进行查询。

$users = $user->get();

宽松模式

两个内置转换都支持looseMode,它将每个文本查询视为一个like查询。如果您已在上文定义了自定义解析器,但没有定义自定义转换(下文),则自定义SearchQueryComponents类型将在标准转换器中作为文本处理。

$pdo = new PDO("sqlite:/tmp/foo.sql");

$transform = new \peckrob\SearchParser\Transforms\SQL\SQL("default_field", $pdo);
$transform->looseMode = true;
$where = $transform->transform($x);

返回

`from` = 'foo@example.com' and `default_field` like '%bar baz%' and `default_field` not like '%meef%' and (`date` between '2018/01/01' and '2018/08/01') and `default_field` like '%#hashtag%'

定义自定义组件转换

通常您可以自由地转换数据,并且如果您不想使用任何内置转换,则不需要使用它们。但是,内置转换也支持自定义组件转换,它们将在运行所有转换之前调用。如果您没有定义自定义转换,则自定义解析类型将在标准转换器中作为文本处理。

要创建自己的转换器,实现 \peckrob\SearchParser\Transforms\TransformsComponents 接口并实现 transformPart() 方法。查看 Hashtag 转换器以获取示例。

$pdo = new PDO("sqlite:/tmp/foo.sql");
$transform = new \peckrob\SearchParser\Transforms\SQL\SQL("default_field", $pdo);
$transform->addComponentTransform(new \peckrob\SearchParser\Transforms\SQL\Hashtag("default_field", $pdo));
$where = $transform->transform($x);

返回

`from` = 'foo@example.com' and `default_field` = 'bar baz' and `default_field` != 'meef' and (`date` between '2018/01/01' and '2018/08/01') and hashtag = 'hashtag'

过滤器

关于安全性的说明

SQL 转换器会转义作为参数传递的数据(这就是为什么你传递 PDO 对象作为上下文的原因),但不会作为字段。Eloquent 转换器在底层很可能以相同的方式工作。

建议的方法是根据白名单过滤字段,并丢弃无效的内容。**不要直接将 SearchQuery 直接传递给 SQL 转换器而不过滤字段**。

内置过滤器

SearchParser 包含了几个内置的过滤器。有 FieldFilterFieldNameMapper。过滤器将按照它们添加到 Filter 对象的顺序执行。

FieldFilter

FieldFilter 是一个简单的有效字段白名单。任何具有字段且不匹配有效字段白名单的 SearchQueryComponent 都将从 SearchQuery 中移除。

$filter = new \peckrob\SearchParser\Filters\Filter();
$field_filter = new \peckrob\SearchParser\Filters\FieldNameMapper();
$field_filter->validFields = ['from'];
$filter->addFilter($field_filter);
$filter->filter($x);

返回

peckrob\SearchParser\SearchQuery Object
(
    [position:peckrob\SearchParser\SearchQuery:private] => 5
    [data:protected] => Array
        (
            [0] => peckrob\SearchParser\SearchQueryComponent Object
                (
                    [type] => field
                    [field] => from
                    [value] => foo@example.com
                    [firstRangeValue] =>
                    [secondRangeValue] =>
                    [negate] =>
                )

            [1] => peckrob\SearchParser\SearchQueryComponent Object
                (
                    [type] => text
                    [field] =>
                    [value] => bar baz
                    [firstRangeValue] =>
                    [secondRangeValue] =>
                    [negate] =>
                )

            [2] => peckrob\SearchParser\SearchQueryComponent Object
                (
                    [type] => text
                    [field] =>
                    [value] => meef
                    [firstRangeValue] =>
                    [secondRangeValue] =>
                    [negate] => 1
                )

            [4] => peckrob\SearchParser\SearchQueryComponent Object
                (
                    [type] => hashtag
                    [field] =>
                    [value] => hashtag
                    [firstRangeValue] =>
                    [secondRangeValue] =>
                    [negate] =>
                )

        )

)

请注意,除了 from 以外的所有字段都已从 SearchQuery 对象中移除。

FieldNameMapper

假设你有一个想要暴露给用户但后端标题不同的字段。例如,对用户来说可能是 date,而在后端可能是 created_on。这就是 FieldNameMapper 过滤器发挥作用的地方。

$filter = new \peckrob\SearchParser\Filters\Filter();
$field_filter = new \peckrob\SearchParser\Filters\FieldNameMapper();
$field_filter->mappingFields = [
    'date' => 'created_on'
];
$filter->addFilter($field_filter);
$filter->filter($x);

返回

...

            [3] => peckrob\SearchParser\SearchQueryComponent Object
                (
                    [type] => range
                    [field] => date_created
                    [value] =>
                    [firstRangeValue] => 2018/01/01
                    [secondRangeValue] => 2018/08/01
                    [negate] =>
                )

定义自定义过滤器

与解析器和转换器一样,定义自定义过滤器非常简单。只需创建一个实现 FiltersQueries 接口的类,并定义一个 filter() 方法。你将接收到 SearchQuery 对象。

SearchQuery 上有一些方便的方法可以使编写过滤器变得更容易。具体来说,它们是

  • remove(SearchQueryComponent $item) - 从 SearchQuery 中移除一个项。
  • replace(SearchQueryComponent $old, SearchQueryComponent $new) - 用新项替换一个项。
  • merge(SearchQuery $query) - 合并两个 SearchQuery 对象。

定义好你的自定义过滤器后,只需在 Filter 实例上调用 addFilter()。同样,过滤器的执行顺序与它们添加的顺序相同。

测试

包含测试。本项目中 phpunit 是 require-dev,因此你需要使用 dev 模式执行 composer 安装。然后只需从项目根目录运行 phpunit。如果未安装可选组件(例如 Eloquent),则某些测试可能被跳过。

作者

Rebecca Peck

许可证

MIT