Skip to content

Commit d921dd3

Browse files
GromNaNsoyuka
andauthored
feat(mongodb): make ParameterExtension context more generic (#7389)
Co-authored-by: soyuka <[email protected]>
1 parent 95451fb commit d921dd3

File tree

4 files changed

+197
-11
lines changed

4 files changed

+197
-11
lines changed

phpstan.neon.dist

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,13 @@ parameters:
113113
# Allow extra assertions in tests: https://github.com/phpstan/phpstan-strict-rules/issues/130
114114
- '#^Call to (static )?method PHPUnit\\Framework\\Assert::.* will always evaluate to true\.$#'
115115

116+
# Unsealed array shapes not supported
117+
-
118+
message: '#^Parameter &\$context by\-ref type of method ApiPlatform\\Doctrine\\Odm\\Extension\\ParameterExtension\:\:applyFilter\(\) expects array\<string, mixed\>, array(.*) given\.$#'
119+
identifier: parameterByRef.type
120+
count: 5
121+
path: src/Doctrine/Odm/Extension/ParameterExtension.php
122+
116123
# Level 6
117124
-
118125
identifier: missingType.iterableValue

src/Doctrine/Odm/Extension/ParameterExtension.php

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,12 @@ private function applyFilter(Builder $aggregationBuilder, ?string $resourceClass
8989
$filter->setProperties($properties ?? []);
9090
}
9191

92-
$filterContext = ['filters' => $values, 'parameter' => $parameter, 'match' => $context['match'] ?? null];
93-
$filter->apply($aggregationBuilder, $resourceClass, $operation, $filterContext);
94-
// update by reference
95-
if (isset($filterContext['mongodb_odm_sort_fields'])) {
96-
$context['mongodb_odm_sort_fields'] = $filterContext['mongodb_odm_sort_fields'];
97-
}
98-
if (isset($filterContext['match'])) {
99-
$context['match'] = $filterContext['match'];
100-
}
92+
$context['filters'] = $values;
93+
$context['parameter'] = $parameter;
94+
95+
$filter->apply($aggregationBuilder, $resourceClass, $operation, $context);
96+
97+
unset($context['filters'], $context['parameter']);
10198
}
10299

103100
if (isset($context['match'])) {
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
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\Doctrine\Odm\Tests\Extension;
15+
16+
use ApiPlatform\Doctrine\Common\Filter\LoggerAwareInterface;
17+
use ApiPlatform\Doctrine\Common\Filter\LoggerAwareTrait;
18+
use ApiPlatform\Doctrine\Common\Filter\ManagerRegistryAwareInterface;
19+
use ApiPlatform\Doctrine\Common\Filter\ManagerRegistryAwareTrait;
20+
use ApiPlatform\Doctrine\Odm\Extension\ParameterExtension;
21+
use ApiPlatform\Doctrine\Odm\Filter\FilterInterface;
22+
use ApiPlatform\Metadata\BackwardCompatibleFilterDescriptionTrait;
23+
use ApiPlatform\Metadata\GetCollection;
24+
use ApiPlatform\Metadata\Operation;
25+
use ApiPlatform\Metadata\Parameter;
26+
use ApiPlatform\Metadata\QueryParameter;
27+
use Doctrine\Bundle\MongoDBBundle\ManagerRegistry;
28+
use Doctrine\ODM\MongoDB\Aggregation\Builder;
29+
use PHPUnit\Framework\Assert;
30+
use PHPUnit\Framework\TestCase;
31+
use Psr\Container\ContainerInterface;
32+
use Psr\Log\LoggerInterface;
33+
34+
class ParameterExtensionTest extends TestCase
35+
{
36+
public function testApplyToCollectionWithNoParameters(): void
37+
{
38+
$aggregationBuilder = $this->createMock(Builder::class);
39+
$operation = new GetCollection();
40+
$extension = new ParameterExtension($this->createNonCalledFilterLocator());
41+
42+
$context = [];
43+
$extension->applyToCollection($aggregationBuilder, 'SomeClass', $operation, $context);
44+
45+
$this->assertSame([], $context);
46+
}
47+
48+
public function testApplyToCollectionWithParameterAndFilter(): void
49+
{
50+
$filterLocator = $this->createMock(ContainerInterface::class);
51+
$filterLocator->expects($this->once())->method('has')
52+
->with('filter_service_id')
53+
->willReturn(true);
54+
$filterLocator->expects($this->once())->method('get')
55+
->with('filter_service_id')
56+
->willReturn($this->createFilterMock());
57+
58+
$aggregationBuilder = $this->createMock(Builder::class);
59+
$operation = (new GetCollection())
60+
->withParameters([
61+
(new QueryParameter(
62+
key: 'param1',
63+
filter: $this->createFilterMock(),
64+
))->setValue(1),
65+
(new QueryParameter(
66+
key: 'param2',
67+
filter: 'filter_service_id' // From the container
68+
))->setValue(2),
69+
new QueryParameter(
70+
key: 'param3',
71+
// Filer not called because no value
72+
filter: $this->createFilterMock(false)
73+
),
74+
new QueryParameter(
75+
key: 'param4',
76+
// Filer not called because no value
77+
filter: 'filter_service_id_not_called'
78+
),
79+
]);
80+
$extension = new ParameterExtension($filterLocator);
81+
82+
$context = [];
83+
$extension->applyToCollection($aggregationBuilder, 'SomeClass', $operation, $context);
84+
85+
$this->assertSame([], $context);
86+
}
87+
88+
public function testApplyToCollectionWithLoggerAndManagerRegistry(): void
89+
{
90+
$aggregationBuilder = $this->createMock(Builder::class);
91+
92+
$filter = new class implements FilterInterface, LoggerAwareInterface, ManagerRegistryAwareInterface {
93+
use BackwardCompatibleFilterDescriptionTrait;
94+
use LoggerAwareTrait;
95+
use ManagerRegistryAwareTrait;
96+
97+
public function apply(Builder $aggregationBuilder, string $resourceClass, ?Operation $operation = null, array &$context = []): void
98+
{
99+
Assert::assertNotNull($this->logger);
100+
Assert::assertNotNull($this->managerRegistry);
101+
Assert::assertSame('SomeClass', $resourceClass);
102+
}
103+
};
104+
105+
$operation = (new GetCollection())
106+
->withParameters([
107+
(new QueryParameter(
108+
key: 'param1',
109+
filter: $filter,
110+
))->setValue(1),
111+
]);
112+
113+
$extension = new ParameterExtension(
114+
$this->createNonCalledFilterLocator(),
115+
$this->createMock(ManagerRegistry::class),
116+
$this->createMock(LoggerInterface::class),
117+
);
118+
$context = [];
119+
$extension->applyToCollection($aggregationBuilder, 'SomeClass', $operation, $context);
120+
121+
$this->assertSame([], $context);
122+
$this->assertNotNull($filter->getLogger());
123+
$this->assertNotNull($filter->getManagerRegistry());
124+
}
125+
126+
public function testApplyToCollectionPassesContext(): void
127+
{
128+
$aggregationBuilder = $this->createMock(Builder::class);
129+
130+
$filter = new class implements FilterInterface {
131+
use BackwardCompatibleFilterDescriptionTrait;
132+
133+
public function apply(Builder $aggregationBuilder, string $resourceClass, ?Operation $operation = null, array &$context = []): void
134+
{
135+
Assert::assertIsArray($context['filters']);
136+
Assert::assertInstanceOf(Parameter::class, $context['parameter']);
137+
$context['check_the_filters'][] = $context['filters'];
138+
}
139+
};
140+
141+
$operation = (new GetCollection())
142+
->withParameters([
143+
(new QueryParameter(
144+
key: 'param1',
145+
filter: $filter,
146+
))->setValue(1),
147+
(new QueryParameter(
148+
key: 'param2',
149+
filter: $filter,
150+
))->setValue(2),
151+
]);
152+
153+
$extension = new ParameterExtension($this->createNonCalledFilterLocator());
154+
$context = [];
155+
$extension->applyToCollection($aggregationBuilder, 'SomeClass', $operation, $context);
156+
157+
$this->assertSame([
158+
'check_the_filters' => [
159+
['param1' => 1],
160+
['param2' => 2],
161+
],
162+
], $context);
163+
}
164+
165+
private function createFilterMock(bool $expectCall = true): FilterInterface
166+
{
167+
$filter = $this->createMock(FilterInterface::class);
168+
$filter->expects($expectCall ? $this->once() : $this->never())
169+
->method('apply');
170+
171+
return $filter;
172+
}
173+
174+
private function createNonCalledFilterLocator(): ContainerInterface
175+
{
176+
$filterLocator = $this->createMock(ContainerInterface::class);
177+
$filterLocator->expects($this->never())->method('has');
178+
$filterLocator->expects($this->never())->method('get');
179+
180+
return $filterLocator;
181+
}
182+
}

src/Doctrine/Odm/composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
],
2626
"require": {
2727
"php": ">=8.2",
28-
"api-platform/doctrine-common": "^4.1.11",
29-
"api-platform/metadata": "^4.1.11",
28+
"api-platform/doctrine-common": "^4.2.0@beta",
29+
"api-platform/metadata": "^4.2.0@beta",
3030
"api-platform/state": "^4.1.11",
3131
"doctrine/mongodb-odm": "^2.10",
3232
"symfony/property-info": "^6.4 || ^7.1",

0 commit comments

Comments
 (0)