Skip to content

Commit c74675d

Browse files
committed
refactor: range filter
start for api-platform#4689
1 parent 45831a9 commit c74675d

File tree

3 files changed

+133
-102
lines changed

3 files changed

+133
-102
lines changed

src/Doctrine/Odm/Filter/RangeFilter.php

Lines changed: 42 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
namespace ApiPlatform\Doctrine\Odm\Filter;
1515

1616
use ApiPlatform\Doctrine\Common\Filter\RangeFilterInterface;
17-
use ApiPlatform\Doctrine\Common\Filter\RangeFilterTrait;
17+
use ApiPlatform\Metadata\BackwardCompatibleFilterDescriptionTrait;
18+
use ApiPlatform\Metadata\JsonSchemaFilterInterface;
1819
use ApiPlatform\Metadata\OpenApiParameterFilterInterface;
1920
use ApiPlatform\Metadata\Operation;
2021
use ApiPlatform\Metadata\Parameter;
@@ -32,12 +33,17 @@
3233
* ```php
3334
* <?php
3435
* // api/src/Entity/Book.php
35-
* use ApiPlatform\Metadata\ApiFilter;
3636
* use ApiPlatform\Metadata\ApiResource;
37+
* use ApiPlatform\Metadata\GetCollection;
38+
* use ApiPlatform\Metadata\QueryParameter;
3739
* use ApiPlatform\Doctrine\Odm\Filter\RangeFilter;
3840
*
3941
* #[ApiResource]
40-
* #[ApiFilter(RangeFilter::class, properties: ['price'])]
42+
* #[GetCollection(
43+
* parameters: [
44+
* 'price' => new QueryParameter(filter: new RangeFilter())
45+
* ]
46+
* )]
4147
* class Book
4248
* {
4349
* // ...
@@ -108,107 +114,94 @@
108114
* @author Lee Siong Chan <ahlee2326@me.com>
109115
* @author Alan Poulain <contact@alanpoulain.eu>
110116
*/
111-
final class RangeFilter extends AbstractFilter implements RangeFilterInterface, OpenApiParameterFilterInterface
117+
final class RangeFilter implements FilterInterface, RangeFilterInterface, JsonSchemaFilterInterface, OpenApiParameterFilterInterface
112118
{
113-
use RangeFilterTrait;
119+
use BackwardCompatibleFilterDescriptionTrait;
114120

115-
/**
116-
* {@inheritdoc}
117-
*/
118-
protected function filterProperty(string $property, mixed $values, Builder $aggregationBuilder, string $resourceClass, ?Operation $operation = null, array &$context = []): void
121+
public function apply(Builder $aggregationBuilder, string $resourceClass, ?Operation $operation = null, array $context = []): void
119122
{
120-
if (
121-
!\is_array($values)
122-
|| !$this->isPropertyEnabled($property, $resourceClass)
123-
|| !$this->isPropertyMapped($property, $resourceClass)
124-
) {
123+
$parameter = $context['parameter'] ?? null;
124+
if (!$parameter) {
125125
return;
126126
}
127127

128-
$values = $this->normalizeValues($values, $property);
129-
if (null === $values) {
128+
$values = $parameter->getValue();
129+
if (!\is_array($values)) {
130130
return;
131131
}
132132

133-
$matchField = $field = $property;
134-
135-
if ($this->isPropertyNested($property, $resourceClass)) {
136-
[$matchField] = $this->addLookupsForNestedProperty($property, $aggregationBuilder, $resourceClass);
137-
}
133+
$property = $parameter->getProperty();
138134

139135
foreach ($values as $operator => $value) {
140-
$this->addMatch(
141-
$aggregationBuilder,
142-
$field,
143-
$matchField,
144-
$operator,
145-
$value
146-
);
136+
$this->addMatch($aggregationBuilder, $property, $operator, $value);
147137
}
148138
}
149139

150-
/**
151-
* Adds the match stage according to the operator.
152-
*/
153-
protected function addMatch(Builder $aggregationBuilder, string $field, string $matchField, string $operator, string $value): void
140+
private function addMatch(Builder $aggregationBuilder, string $field, string $operator, string $value): void
154141
{
155142
switch ($operator) {
156143
case self::PARAMETER_BETWEEN:
157144
$rangeValue = explode('..', $value, 2);
158145

159-
$rangeValue = $this->normalizeBetweenValues($rangeValue);
160-
if (null === $rangeValue) {
146+
if (2 !== \count($rangeValue)) {
147+
return;
148+
}
149+
150+
if (!is_numeric($rangeValue[0]) || !is_numeric($rangeValue[1])) {
161151
return;
162152
}
163153

154+
$rangeValue = [$rangeValue[0] + 0, $rangeValue[1] + 0];
155+
164156
if ($rangeValue[0] === $rangeValue[1]) {
165-
$aggregationBuilder->match()->field($matchField)->equals($rangeValue[0]);
157+
$aggregationBuilder->match()->field($field)->equals($rangeValue[0]);
166158

167159
return;
168160
}
169161

170-
$aggregationBuilder->match()->field($matchField)->gte($rangeValue[0])->lte($rangeValue[1]);
162+
$aggregationBuilder->match()->field($field)->gte($rangeValue[0])->lte($rangeValue[1]);
171163

172164
break;
173165
case self::PARAMETER_GREATER_THAN:
174-
$value = $this->normalizeValue($value, $operator);
175-
if (null === $value) {
166+
if (!is_numeric($value)) {
176167
return;
177168
}
178169

179-
$aggregationBuilder->match()->field($matchField)->gt($value);
170+
$aggregationBuilder->match()->field($field)->gt($value + 0);
180171

181172
break;
182173
case self::PARAMETER_GREATER_THAN_OR_EQUAL:
183-
$value = $this->normalizeValue($value, $operator);
184-
if (null === $value) {
174+
if (!is_numeric($value)) {
185175
return;
186176
}
187177

188-
$aggregationBuilder->match()->field($matchField)->gte($value);
178+
$aggregationBuilder->match()->field($field)->gte($value + 0);
189179

190180
break;
191181
case self::PARAMETER_LESS_THAN:
192-
$value = $this->normalizeValue($value, $operator);
193-
if (null === $value) {
182+
if (!is_numeric($value)) {
194183
return;
195184
}
196185

197-
$aggregationBuilder->match()->field($matchField)->lt($value);
186+
$aggregationBuilder->match()->field($field)->lt($value + 0);
198187

199188
break;
200189
case self::PARAMETER_LESS_THAN_OR_EQUAL:
201-
$value = $this->normalizeValue($value, $operator);
202-
if (null === $value) {
190+
if (!is_numeric($value)) {
203191
return;
204192
}
205193

206-
$aggregationBuilder->match()->field($matchField)->lte($value);
194+
$aggregationBuilder->match()->field($field)->lte($value + 0);
207195

208196
break;
209197
}
210198
}
211199

200+
public function getSchema(Parameter $parameter): array
201+
{
202+
return ['type' => 'number'];
203+
}
204+
212205
public function getOpenApiParameters(Parameter $parameter): array
213206
{
214207
$in = $parameter instanceof QueryParameter ? 'query' : 'header';
@@ -219,6 +212,7 @@ public function getOpenApiParameters(Parameter $parameter): array
219212
new OpenApiParameter(name: $key.'[lt]', in: $in),
220213
new OpenApiParameter(name: $key.'[gte]', in: $in),
221214
new OpenApiParameter(name: $key.'[lte]', in: $in),
215+
new OpenApiParameter(name: $key.'[between]', in: $in),
222216
];
223217
}
224218
}

src/Doctrine/Orm/Filter/RangeFilter.php

Lines changed: 40 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
namespace ApiPlatform\Doctrine\Orm\Filter;
1515

1616
use ApiPlatform\Doctrine\Common\Filter\RangeFilterInterface;
17-
use ApiPlatform\Doctrine\Common\Filter\RangeFilterTrait;
1817
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
18+
use ApiPlatform\Metadata\BackwardCompatibleFilterDescriptionTrait;
19+
use ApiPlatform\Metadata\JsonSchemaFilterInterface;
1920
use ApiPlatform\Metadata\OpenApiParameterFilterInterface;
2021
use ApiPlatform\Metadata\Operation;
2122
use ApiPlatform\Metadata\Parameter;
2223
use ApiPlatform\Metadata\QueryParameter;
2324
use ApiPlatform\OpenApi\Model\Parameter as OpenApiParameter;
24-
use Doctrine\ORM\Query\Expr\Join;
2525
use Doctrine\ORM\QueryBuilder;
2626

2727
/**
@@ -34,12 +34,17 @@
3434
* ```php
3535
* <?php
3636
* // api/src/Entity/Book.php
37-
* use ApiPlatform\Metadata\ApiFilter;
3837
* use ApiPlatform\Metadata\ApiResource;
38+
* use ApiPlatform\Metadata\GetCollection;
39+
* use ApiPlatform\Metadata\QueryParameter;
3940
* use ApiPlatform\Doctrine\Orm\Filter\RangeFilter;
4041
*
4142
* #[ApiResource]
42-
* #[ApiFilter(RangeFilter::class, properties: ['price'])]
43+
* #[GetCollection(
44+
* parameters: [
45+
* 'price' => new QueryParameter(filter: new RangeFilter())
46+
* ]
47+
* )]
4348
* class Book
4449
* {
4550
* // ...
@@ -109,63 +114,48 @@
109114
*
110115
* @author Lee Siong Chan <ahlee2326@me.com>
111116
*/
112-
final class RangeFilter extends AbstractFilter implements RangeFilterInterface, OpenApiParameterFilterInterface
117+
final class RangeFilter implements FilterInterface, RangeFilterInterface, JsonSchemaFilterInterface, OpenApiParameterFilterInterface
113118
{
114-
use RangeFilterTrait;
119+
use BackwardCompatibleFilterDescriptionTrait;
115120

116-
/**
117-
* {@inheritdoc}
118-
*/
119-
protected function filterProperty(string $property, mixed $values, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
121+
public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
120122
{
121-
if (
122-
!\is_array($values)
123-
|| !$this->isPropertyEnabled($property, $resourceClass)
124-
|| !$this->isPropertyMapped($property, $resourceClass)
125-
) {
123+
$parameter = $context['parameter'] ?? null;
124+
if (!$parameter) {
126125
return;
127126
}
128127

129-
$values = $this->normalizeValues($values, $property);
130-
if (null === $values) {
128+
$values = $parameter->getValue();
129+
if (!\is_array($values)) {
131130
return;
132131
}
133132

133+
$property = $parameter->getProperty();
134134
$alias = $queryBuilder->getRootAliases()[0];
135-
$field = $property;
136-
137-
if ($this->isPropertyNested($property, $resourceClass)) {
138-
[$alias, $field] = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass, Join::INNER_JOIN);
139-
}
140135

141136
foreach ($values as $operator => $value) {
142-
$this->addWhere(
143-
$queryBuilder,
144-
$queryNameGenerator,
145-
$alias,
146-
$field,
147-
$operator,
148-
$value
149-
);
137+
$this->addWhere($queryBuilder, $queryNameGenerator, $alias, $property, $operator, $value);
150138
}
151139
}
152140

153-
/**
154-
* Adds the where clause according to the operator.
155-
*/
156-
protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, string $operator, string $value): void
141+
private function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, string $operator, string $value): void
157142
{
158143
$valueParameter = $queryNameGenerator->generateParameterName($field);
159144

160145
switch ($operator) {
161146
case self::PARAMETER_BETWEEN:
162147
$rangeValue = explode('..', $value, 2);
163148

164-
$rangeValue = $this->normalizeBetweenValues($rangeValue);
165-
if (null === $rangeValue) {
149+
if (2 !== \count($rangeValue)) {
150+
return;
151+
}
152+
153+
if (!is_numeric($rangeValue[0]) || !is_numeric($rangeValue[1])) {
166154
return;
167155
}
168156

157+
$rangeValue = [$rangeValue[0] + 0, $rangeValue[1] + 0];
158+
169159
if ($rangeValue[0] === $rangeValue[1]) {
170160
$queryBuilder
171161
->andWhere(\sprintf('%s.%s = :%s', $alias, $field, $valueParameter))
@@ -181,52 +171,53 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf
181171

182172
break;
183173
case self::PARAMETER_GREATER_THAN:
184-
$value = $this->normalizeValue($value, $operator);
185-
if (null === $value) {
174+
if (!is_numeric($value)) {
186175
return;
187176
}
188177

189178
$queryBuilder
190179
->andWhere(\sprintf('%s.%s > :%s', $alias, $field, $valueParameter))
191-
->setParameter($valueParameter, $value);
180+
->setParameter($valueParameter, $value + 0);
192181

193182
break;
194183
case self::PARAMETER_GREATER_THAN_OR_EQUAL:
195-
$value = $this->normalizeValue($value, $operator);
196-
if (null === $value) {
184+
if (!is_numeric($value)) {
197185
return;
198186
}
199187

200188
$queryBuilder
201189
->andWhere(\sprintf('%s.%s >= :%s', $alias, $field, $valueParameter))
202-
->setParameter($valueParameter, $value);
190+
->setParameter($valueParameter, $value + 0);
203191

204192
break;
205193
case self::PARAMETER_LESS_THAN:
206-
$value = $this->normalizeValue($value, $operator);
207-
if (null === $value) {
194+
if (!is_numeric($value)) {
208195
return;
209196
}
210197

211198
$queryBuilder
212199
->andWhere(\sprintf('%s.%s < :%s', $alias, $field, $valueParameter))
213-
->setParameter($valueParameter, $value);
200+
->setParameter($valueParameter, $value + 0);
214201

215202
break;
216203
case self::PARAMETER_LESS_THAN_OR_EQUAL:
217-
$value = $this->normalizeValue($value, $operator);
218-
if (null === $value) {
204+
if (!is_numeric($value)) {
219205
return;
220206
}
221207

222208
$queryBuilder
223209
->andWhere(\sprintf('%s.%s <= :%s', $alias, $field, $valueParameter))
224-
->setParameter($valueParameter, $value);
210+
->setParameter($valueParameter, $value + 0);
225211

226212
break;
227213
}
228214
}
229215

216+
public function getSchema(Parameter $parameter): array
217+
{
218+
return ['type' => 'number'];
219+
}
220+
230221
public function getOpenApiParameters(Parameter $parameter): array
231222
{
232223
$in = $parameter instanceof QueryParameter ? 'query' : 'header';
@@ -237,6 +228,7 @@ public function getOpenApiParameters(Parameter $parameter): array
237228
new OpenApiParameter(name: $key.'[lt]', in: $in),
238229
new OpenApiParameter(name: $key.'[gte]', in: $in),
239230
new OpenApiParameter(name: $key.'[lte]', in: $in),
231+
new OpenApiParameter(name: $key.'[between]', in: $in),
240232
];
241233
}
242234
}

0 commit comments

Comments
 (0)