Skip to content

Commit 34604f4

Browse files
committed
fix(hydra): iri template when using query parameter
1 parent 77d3ff3 commit 34604f4

File tree

5 files changed

+97
-46
lines changed

5 files changed

+97
-46
lines changed

src/Hydra/Serializer/CollectionFiltersNormalizer.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ private function getSearch(string $resourceClass, array $parts, array $filters,
171171
continue;
172172
}
173173

174-
if (!($property = $parameter->getProperty()) && ($filterId = $parameter->getFilter()) && ($filter = $this->getFilter($filterId))) {
174+
if (($filterId = $parameter->getFilter()) && ($filter = $this->getFilter($filterId))) {
175175
foreach ($filter->getDescription($resourceClass) as $variable => $description) {
176176
// This is a practice induced by PHP and is not necessary when implementing URI template
177177
if (str_ends_with((string) $variable, '[]')) {
@@ -192,7 +192,7 @@ private function getSearch(string $resourceClass, array $parts, array $filters,
192192
continue;
193193
}
194194

195-
if (!$property) {
195+
if (!($property = $parameter->getProperty())) {
196196
continue;
197197
}
198198

src/Metadata/Resource/Factory/ParameterResourceMetadataCollectionFactory.php

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -126,38 +126,10 @@ private function setDefaults(string $key, Parameter $parameter, string $resource
126126
$parameter = $parameter->withSchema($schema);
127127
}
128128

129-
if (null === $parameter->getProperty() && ($property = $description[$key]['property'] ?? null)) {
130-
$parameter = $parameter->withProperty($property);
131-
}
132-
133129
if (null === $parameter->getRequired() && ($required = $description[$key]['required'] ?? null)) {
134130
$parameter = $parameter->withRequired($required);
135131
}
136132

137-
if (null === $parameter->getOpenApi() && $openApi = $description[$key]['openapi'] ?? null) {
138-
if ($openApi instanceof OpenApiParameter) {
139-
$parameter = $parameter->withOpenApi($openApi);
140-
} elseif (\is_array($openApi)) {
141-
$schema = $schema ?? $openApi['schema'] ?? [];
142-
$parameter = $parameter->withOpenApi(new OpenApiParameter(
143-
$key,
144-
$parameter instanceof HeaderParameterInterface ? 'header' : 'query',
145-
$description[$key]['description'] ?? '',
146-
$description[$key]['required'] ?? $openApi['required'] ?? false,
147-
$openApi['deprecated'] ?? false,
148-
$openApi['allowEmptyValue'] ?? true,
149-
$schema,
150-
$openApi['style'] ?? null,
151-
$openApi['explode'] ?? ('array' === ($schema['type'] ?? null)),
152-
$openApi['allowReserved'] ?? false,
153-
$openApi['example'] ?? null,
154-
isset(
155-
$openApi['examples']
156-
) ? new \ArrayObject($openApi['examples']) : null
157-
));
158-
}
159-
}
160-
161133
$schema = $parameter->getSchema() ?? (($openApi = $parameter->getOpenApi()) ? $openApi->getSchema() : null);
162134

163135
// Only add validation if the Symfony Validator is installed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource;
4+
5+
use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
6+
use ApiPlatform\Doctrine\Orm\State\CollectionProvider;
7+
use ApiPlatform\Doctrine\Orm\State\Options;
8+
use ApiPlatform\Metadata\ApiFilter;
9+
use ApiPlatform\Metadata\GetCollection;
10+
use ApiPlatform\Metadata\QueryParameter;
11+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\FilterWithStateOptionsEntity;
12+
13+
#[GetCollection(
14+
uriTemplate: 'filter_with_state_options',
15+
stateOptions: new Options(entityClass: FilterWithStateOptionsEntity::class),
16+
parameters: ['date' => new QueryParameter(filter: 'filter_with_state_options_date', property: 'dummyDate')],
17+
provider: CollectionProvider::class
18+
)]
19+
#[ApiFilter(DateFilter::class, alias: 'filter_with_state_options_date', properties: ['dummyDate' => DateFilter::EXCLUDE_NULL])]
20+
final readonly class FilterWithStateOptions
21+
{
22+
public function __construct(public string $id, public \DateImmutable $dummyDate, public string $name) {}
23+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;
4+
5+
use Doctrine\ORM\Mapping as ORM;
6+
7+
#[ORM\Entity]
8+
class FilterWithStateOptionsEntity
9+
{
10+
public function __construct(
11+
#[ORM\Column(type: 'integer')]
12+
#[ORM\Id]
13+
#[ORM\GeneratedValue(strategy: 'AUTO')]
14+
public ?int $id = null,
15+
#[ORM\Column(type: 'date_immutable', nullable: true)]
16+
public ?\DateTimeImmutable $dummyDate = null,
17+
#[ORM\Column(type: 'string', nullable: true)]
18+
public ?string $name = null
19+
) {}
20+
}

tests/Functional/Parameters/DoctrineTest.php

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,22 @@
1515

1616
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
1717
use ApiPlatform\Tests\Fixtures\TestBundle\Document\SearchFilterParameterDocument;
18+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\FilterWithStateOptionsEntity;
1819
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SearchFilterParameter;
1920
use Doctrine\ORM\EntityManagerInterface;
2021
use Doctrine\ORM\Tools\SchemaTool;
22+
use Symfony\Component\DependencyInjection\ContainerInterface;
2123

2224
final class DoctrineTest extends ApiTestCase
2325
{
2426
public function testDoctrineEntitySearchFilter(): void
2527
{
26-
$this->recreateSchema();
27-
$container = static::getContainer();
28-
$route = 'mongodb' === $container->getParameter('kernel.environment') ? 'search_filter_parameter_document' : 'search_filter_parameter';
28+
static::bootKernel();
29+
$container = static::$kernel->getContainer();
30+
$resource = $this->isMongoDb($container) ? SearchFilterParameterDocument::class : SearchFilterParameter::class;
31+
$this->recreateSchema($resource);
32+
$this->createFixture($resource);
33+
$route = $this->isMongoDb($container) ? 'search_filter_parameter_document' : 'search_filter_parameter';
2934
$response = self::createClient()->request('GET', $route.'?foo=bar');
3035
$a = $response->toArray();
3136
$this->assertCount(2, $a['hydra:member']);
@@ -69,9 +74,12 @@ public function testGraphQl(): void
6974
$this->markTestSkipped('Parameters are not supported in BC mode.');
7075
}
7176

72-
$this->recreateSchema();
73-
$container = static::getContainer();
74-
$object = 'mongodb' === $container->getParameter('kernel.environment') ? 'searchFilterParameterDocuments' : 'searchFilterParameters';
77+
static::bootKernel();
78+
$container = static::$kernel->getContainer();
79+
$resource = $this->isMongoDb($container) ? SearchFilterParameterDocument::class : SearchFilterParameter::class;
80+
$this->recreateSchema($resource);
81+
$this->createFixture($resource);
82+
$object = $this->isMongoDb($container) ? 'searchFilterParameterDocuments' : 'searchFilterParameters';
7583
$response = self::createClient()->request('POST', '/graphql', ['json' => [
7684
'query' => \sprintf('{ %s(foo: "bar") { edges { node { id foo createdAt } } } }', $object),
7785
]]);
@@ -93,31 +101,54 @@ public function testGraphQl(): void
93101
$this->assertArraySubset(['foo' => 'bar', 'createdAt' => '2024-01-21T00:00:00+00:00'], $response->toArray()['data'][$object]['edges'][0]['node']);
94102
}
95103

96-
/**
97-
* @param array<string, mixed> $options kernel options
98-
*/
99-
private function recreateSchema(array $options = []): void
104+
public function testStateOptions(): void
100105
{
101-
self::bootKernel($options);
106+
static::bootKernel();
107+
$container = static::$kernel->getContainer();
108+
$this->recreateSchema(FilterWithStateOptionsEntity::class);
109+
$registry = $this->isMongoDb($container) ? $container->get('doctrine_mongodb') : $container->get('doctrine');
110+
$manager = $registry->getManager();
111+
$d = new \DateTimeImmutable();
112+
$manager->persist(new FilterWithStateOptionsEntity(dummyDate: $d, name: 'current'));
113+
$manager->persist(new FilterWithStateOptionsEntity(name: 'null'));
114+
$manager->persist(new FilterWithStateOptionsEntity(dummyDate: $d->add(\DateInterval::createFromDateString('1 day')), name: 'after'));
115+
$manager->flush();
116+
$response = self::createClient()->request('GET', 'filter_with_state_options?date[before]='.$d->format('Y-m-d'));
117+
$a = $response->toArray();
118+
$this->assertEquals('/filter_with_state_options{?date[before],date[strictly_before],date[after],date[strictly_after]}', $a['hydra:search']['hydra:template']);
119+
$this->assertCount(1, $a['hydra:member']);
120+
$this->assertEquals('current', $a['hydra:member'][0]['name']);
121+
$response = self::createClient()->request('GET', 'filter_with_state_options?date[strictly_after]='.$d->format('Y-m-d'));
122+
$a = $response->toArray();
123+
$this->assertCount(1, $a['hydra:member']);
124+
$this->assertEquals('after', $a['hydra:member'][0]['name']);
125+
}
102126

103-
$container = static::getContainer();
104-
$registry = $this->getContainer()->get('mongodb' === $container->getParameter('kernel.environment') ? 'doctrine_mongodb' : 'doctrine');
105-
$resource = 'mongodb' === $container->getParameter('kernel.environment') ? SearchFilterParameterDocument::class : SearchFilterParameter::class;
127+
private function recreateSchema(string $resourceClass): void
128+
{
129+
$container = static::$kernel->getContainer();
130+
$registry = $this->isMongoDb($container) ? $container->get('doctrine_mongodb') : $container->get('doctrine');
106131
$manager = $registry->getManager();
107132

108133
if ($manager instanceof EntityManagerInterface) {
109-
$classes = $manager->getClassMetadata($resource);
134+
$classes = $manager->getClassMetadata($resourceClass);
110135
$schemaTool = new SchemaTool($manager);
111136
@$schemaTool->dropSchema([$classes]);
112137
@$schemaTool->createSchema([$classes]);
113138
} else {
114139
$schemaManager = $manager->getSchemaManager();
115140
$schemaManager->dropCollections();
116141
}
142+
}
117143

144+
public function createFixture(string $resourceClass): void
145+
{
146+
$container = static::$kernel->getContainer();
147+
$registry = $this->isMongoDb($container) ? $container->get('doctrine_mongodb') : $container->get('doctrine');
148+
$manager = $registry->getManager();
118149
$date = new \DateTimeImmutable('2024-01-21');
119150
foreach (['foo', 'foo', 'foo', 'bar', 'bar', 'baz'] as $t) {
120-
$s = new $resource();
151+
$s = new $resourceClass();
121152
$s->setFoo($t);
122153
if ('bar' === $t) {
123154
$s->setCreatedAt($date);
@@ -128,4 +159,9 @@ private function recreateSchema(array $options = []): void
128159
}
129160
$manager->flush();
130161
}
162+
163+
private function isMongoDb(ContainerInterface $container): bool
164+
{
165+
return 'mongodb' === $container->getParameter('kernel.environment');
166+
}
131167
}

0 commit comments

Comments
 (0)