Skip to content

Commit 11010c6

Browse files
committed
feat: add elasticsearch RangeFilter support
1 parent 61c6dee commit 11010c6

File tree

2 files changed

+138
-0
lines changed

2 files changed

+138
-0
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Elasticsearch\Filter;
15+
16+
/**
17+
* The range filter allows to find resources that [range](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-query.html) the specified text on full text fields.
18+
*
19+
* Syntax: `?property[]=value`.
20+
*
21+
* <div data-code-selector>
22+
*
23+
* ```php
24+
* <?php
25+
* // api/src/Entity/Book.php
26+
* use ApiPlatform\Metadata\ApiFilter;
27+
* use ApiPlatform\Metadata\ApiResource;
28+
* use ApiPlatform\Elasticsearch\Filter\RangeFilter;
29+
*
30+
* #[ApiResource]
31+
* #[ApiFilter(RangeFilter::class, properties: ['title'])]
32+
* class Book
33+
* {
34+
* // ...
35+
* }
36+
* ```
37+
*
38+
* ```yaml
39+
* # config/services.yaml
40+
* services:
41+
* book.range_filter:
42+
* parent: 'api_platform.elasticsearch.range_filter'
43+
* arguments: [ { title: ~ } ]
44+
* tags: [ 'api_platform.filter' ]
45+
* # The following are mandatory only if a _defaults section is defined with inverted values.
46+
* # You may want to isolate filters in a dedicated file to avoid adding the following lines (by adding them in the defaults section)
47+
* autowire: false
48+
* autoconfigure: false
49+
* public: false
50+
*
51+
* # api/config/api_platform/resources.yaml
52+
* resources:
53+
* App\Entity\Book:
54+
* - operations:
55+
* ApiPlatform\Metadata\GetCollection:
56+
* filters: ['book.range_filter']
57+
* ```
58+
*
59+
* ```xml
60+
* <?xml version="1.0" encoding="UTF-8" ?>
61+
* <!-- api/config/services.xml -->
62+
* <?xml version="1.0" encoding="UTF-8" ?>
63+
* <container
64+
* xmlns="http://symfony.com/schema/dic/services"
65+
* xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
66+
* xsi:schemaLocation="http://symfony.com/schema/dic/services
67+
* https://symfony.com/schema/dic/services/services-1.0.xsd">
68+
* <services>
69+
* <service id="book.range_filter" parent="api_platform.elasticsearch.range_filter">
70+
* <argument type="collection">
71+
* <argument key="title"/>
72+
* </argument>
73+
* <tag name="api_platform.filter"/>
74+
* </service>
75+
* </services>
76+
* </container>
77+
* <!-- api/config/api_platform/resources.xml -->
78+
* <resources
79+
* xmlns="https://api-platform.com/schema/metadata/resources-3.0"
80+
* xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
81+
* xsi:schemaLocation="https://api-platform.com/schema/metadata/resources-3.0
82+
* https://api-platform.com/schema/metadata/resources-3.0.xsd">
83+
* <resource class="App\Entity\Book">
84+
* <operations>
85+
* <operation class="ApiPlatform\Metadata\GetCollection">
86+
* <filters>
87+
* <filter>book.range_filter</filter>
88+
* </filters>
89+
* </operation>
90+
* </operations>
91+
* </resource>
92+
* </resources>
93+
* ```
94+
*
95+
* </div>
96+
*
97+
* Given that the collection endpoint is `/books`, you can filter books by title content with the following query: `/books?title=Foundation`.
98+
*
99+
* @see https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-query.html
100+
*
101+
* @author Saifallah Azzabi <seifallah.azzabi@gmail.com>
102+
*/
103+
final class RangeFilter extends AbstractSearchFilter
104+
{
105+
public const GT = 'gt';
106+
107+
public const GTE = 'gte';
108+
109+
public const LT = 'lt';
110+
111+
public const LTE = 'lte';
112+
/**
113+
* {@inheritdoc}
114+
*/
115+
protected function getQuery(string $property, array $values, ?string $nestedPath): array
116+
{
117+
$rangeQuery = ['range' => [$property => []]];
118+
119+
foreach ($values as $operator => $value) {
120+
if (\in_array($operator, [self::GT, self::GTE, self::LT, self::LTE], true)) {
121+
$rangeQuery['range'][$property][$operator] = $value;
122+
}
123+
}
124+
125+
if (null !== $nestedPath) {
126+
return ['nested' => ['path' => $nestedPath, 'query' => $rangeQuery]];
127+
}
128+
129+
return $rangeQuery;
130+
}
131+
}

src/Symfony/Bundle/Resources/config/elasticsearch.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use ApiPlatform\Elasticsearch\Extension\SortFilterExtension;
1919
use ApiPlatform\Elasticsearch\Filter\MatchFilter;
2020
use ApiPlatform\Elasticsearch\Filter\OrderFilter;
21+
use ApiPlatform\Elasticsearch\Filter\RangeFilter;
2122
use ApiPlatform\Elasticsearch\Filter\TermFilter;
2223
use ApiPlatform\Elasticsearch\Metadata\Resource\Factory\ElasticsearchProviderResourceMetadataCollectionFactory;
2324
use ApiPlatform\Elasticsearch\Serializer\DocumentNormalizer;
@@ -93,6 +94,12 @@
9394

9495
$services->alias(MatchFilter::class, 'api_platform.elasticsearch.match_filter');
9596

97+
$services->set('api_platform.elasticsearch.range_filter', RangeFilter::class)
98+
->abstract()
99+
->parent('api_platform.elasticsearch.search_filter');
100+
101+
$services->alias(RangeFilter::class, 'api_platform.elasticsearch.range_filter');
102+
96103
$services->set('api_platform.elasticsearch.order_filter', OrderFilter::class)
97104
->abstract()
98105
->args([

0 commit comments

Comments
 (0)