peckrob / search-parser
一个将自由形式的查询转换为中间对象的解析器,该对象可以转换为查询多个后端(SQL、ElasticSearch等)。
Requires (Dev)
- phpunit/phpunit: ^7.4
Suggests
- illuminate/database: Required to use the Eloquent query builder.
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,它被标记化为一个包含一系列表示搜索查询每个逻辑组件的SearchQueryComponent
的SearchQuery
对象。
$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);
这将返回一个包含一系列SearchQueryComponents
的SearchQuery
对象。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 包含了几个内置的过滤器。有 FieldFilter
和 FieldNameMapper
。过滤器将按照它们添加到 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