Skip to content

Commit 383a5fa

Browse files
olinox14Olivier Massot
andauthored
feat(serializer): global defaults.normalization_context.gen_id configuration option (#7775)
* feat: add global defaults.normalization_context.gen_id configuration option * Adds global genId option support Allows configuring the `genId` option globally via `defaults.normalization_context`. Adds new functional tests to verify that `genId` works as expected when configured globally, and that the `genId` option on `ApiProperty` takes precedence over the global option. * lint * fix unit tests --------- Co-authored-by: Olivier Massot <olivier.massot@2iopenservice.fr>
1 parent 6626549 commit 383a5fa

File tree

6 files changed

+228
-3
lines changed

6 files changed

+228
-3
lines changed

src/Serializer/OperationContextTrait.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ protected function createOperationContext(array $context, ?string $resourceClass
3939
// At some point we should merge the jsonld context here, there's a TODO to simplify this somewhere else
4040
if ($propertyMetadata) {
4141
$context['output'] ??= [];
42-
$context['output']['gen_id'] = $propertyMetadata->getGenId() ?? true;
42+
$context['output']['gen_id'] = $propertyMetadata->getGenId() ?? ($context['gen_id'] ?? true);
4343
}
4444

4545
if (!$resourceClass) {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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\Tests\Fixtures\TestBundle\ApiResource\GenIdFalse;
15+
16+
use ApiPlatform\Metadata\ApiProperty;
17+
use ApiPlatform\Metadata\Get;
18+
use ApiPlatform\Metadata\Operation;
19+
use Doctrine\Common\Collections\ArrayCollection;
20+
use Doctrine\Common\Collections\Collection;
21+
22+
#[Get(uriTemplate: '/gen_id_default', provider: [self::class, 'getData'], normalizationContext: ['hydra_prefix' => false])]
23+
class GenIdDefault
24+
{
25+
public function __construct(
26+
public string $id,
27+
#[ApiProperty] public Collection $subresources,
28+
) {
29+
}
30+
31+
public static function getData(Operation $operation, array $uriVariables = [], array $context = []): self
32+
{
33+
return new self(
34+
'1',
35+
new ArrayCollection(
36+
[
37+
new Subresource('foo'),
38+
new Subresource('bar'),
39+
]
40+
)
41+
);
42+
}
43+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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\Tests\Fixtures\TestBundle\ApiResource\GenIdFalse;
15+
16+
use ApiPlatform\Metadata\ApiProperty;
17+
use ApiPlatform\Metadata\Get;
18+
use ApiPlatform\Metadata\Operation;
19+
use Doctrine\Common\Collections\ArrayCollection;
20+
use Doctrine\Common\Collections\Collection;
21+
22+
#[Get(uriTemplate: '/gen_id_truthy', provider: [self::class, 'getData'], normalizationContext: ['hydra_prefix' => false])]
23+
class GenIdTrue
24+
{
25+
public function __construct(
26+
public string $id,
27+
#[ApiProperty(genId: true)] public Collection $subresources,
28+
) {
29+
}
30+
31+
public static function getData(Operation $operation, array $uriVariables = [], array $context = []): self
32+
{
33+
return new self(
34+
'1',
35+
new ArrayCollection(
36+
[
37+
new Subresource('foo'),
38+
new Subresource('bar'),
39+
]
40+
)
41+
);
42+
}
43+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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\Tests\Fixtures\TestBundle\ApiResource\GenIdFalse;
15+
16+
use ApiPlatform\Metadata\ApiProperty;
17+
18+
class Subresource
19+
{
20+
public function __construct(
21+
#[ApiProperty] public string $title,
22+
) {
23+
}
24+
}

tests/Fixtures/app/AppKernel.php

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,15 @@ class AppKernel extends Kernel
5757
{
5858
use MicroKernelTrait;
5959

60-
public function __construct(string $environment, bool $debug)
60+
private $genIdDefault;
61+
62+
public function __construct(string $environment, bool $debug, ?bool $genIdDefault = null)
6163
{
6264
parent::__construct($environment, $debug);
6365

6466
// patch for behat/symfony2-extension not supporting %env(APP_ENV)%
6567
$this->environment = $_SERVER['APP_ENV'] ?? $environment;
68+
$this->genIdDefault = $genIdDefault ?? $_SERVER['GEN_ID_DEFAULT'] ?? null;
6669
}
6770

6871
public function registerBundles(): array
@@ -106,6 +109,11 @@ public function getProjectDir(): string
106109
return __DIR__;
107110
}
108111

112+
public function getCacheDir(): string
113+
{
114+
return parent::getCacheDir().(null !== $this->genIdDefault ? '_gen_id_'.$this->genIdDefault : '');
115+
}
116+
109117
protected function configureRoutes($routes): void
110118
{
111119
$routes->import(__DIR__."/config/routing_{$this->getEnvironment()}.yml");
@@ -114,6 +122,7 @@ protected function configureRoutes($routes): void
114122
protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader): void
115123
{
116124
$c->setParameter('kernel.project_dir', __DIR__);
125+
$c->setParameter('app.gen_id_default', $this->genIdDefault);
117126

118127
$loader->load(__DIR__."/config/config_{$this->getEnvironment()}.yml");
119128

@@ -274,7 +283,12 @@ class_exists(NativePasswordHasher::class) ? 'password_hashers' : 'encoders' => [
274283
'vary' => ['Accept', 'Cookie'],
275284
'public' => true,
276285
],
277-
'normalization_context' => ['skip_null_values' => false],
286+
'normalization_context' => null !== $this->genIdDefault ? [
287+
'skip_null_values' => false,
288+
'gen_id' => (bool) $this->genIdDefault,
289+
] : [
290+
'skip_null_values' => false,
291+
],
278292
'operations' => [
279293
Get::class,
280294
GetCollection::class,
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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\Tests\Functional;
15+
16+
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
17+
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\GenIdFalse\AggregateRating;
18+
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\GenIdFalse\GenIdDefault;
19+
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\GenIdFalse\GenIdTrue;
20+
use ApiPlatform\Tests\SetupClassResourcesTrait;
21+
22+
final class GenIdGlobalOptionTest extends ApiTestCase
23+
{
24+
use SetupClassResourcesTrait;
25+
26+
protected static ?bool $alwaysBootKernel = true;
27+
28+
/**
29+
* @return class-string[]
30+
*/
31+
public static function getResources(): array
32+
{
33+
return [
34+
AggregateRating::class,
35+
GenIdDefault::class,
36+
GenIdTrue::class,
37+
];
38+
}
39+
40+
protected function setUp(): void
41+
{
42+
parent::setUp();
43+
unset($_SERVER['GEN_ID_DEFAULT']);
44+
}
45+
46+
protected function tearDown(): void
47+
{
48+
unset($_SERVER['GEN_ID_DEFAULT']);
49+
parent::tearDown();
50+
}
51+
52+
/**
53+
* When gen_id is globally false and no #[ApiProperty(genId: ...)] on the property,
54+
* the nested object must not expose an @id.
55+
*/
56+
public function testGlobalGenIdFalseDisablesSkolemIdByDefaultOnProperties(): void
57+
{
58+
$_SERVER['GEN_ID_DEFAULT'] = 0; // simulate global defaults.normalization_context.gen_id: false
59+
60+
$response = self::createClient()->request(
61+
'GET',
62+
'/gen_id_default'
63+
);
64+
$this->assertResponseIsSuccessful();
65+
$data = $response->toArray();
66+
67+
$this->assertArrayNotHasKey('@id', $data['subresources'][0]);
68+
}
69+
70+
/**
71+
* #[ApiProperty(genId: true)] on the property must take precedence.
72+
*/
73+
public function testApiPropertyGenIdTrueTakesPrecedenceOverGlobalFalse(): void
74+
{
75+
$_SERVER['GEN_ID_DEFAULT'] = 0; // simulate global defaults.normalization_context.gen_id: false
76+
77+
$response = self::createClient()->request(
78+
'GET',
79+
'/gen_id_truthy'
80+
);
81+
$this->assertResponseIsSuccessful();
82+
$data = $response->toArray();
83+
84+
$this->assertArrayHasKey('@id', $data['subresources'][0]);
85+
}
86+
87+
/**
88+
* Without a global option and without an attribute, genId must be true by default.
89+
*/
90+
public function testWhenNoGlobalOptionAndNoAttributeGenIdIsTrueByDefault(): void
91+
{
92+
$response = self::createClient()->request(
93+
'GET',
94+
'/gen_id_default'
95+
);
96+
$this->assertResponseIsSuccessful();
97+
$data = $response->toArray();
98+
99+
$this->assertArrayHasKey('@id', $data['subresources'][0]);
100+
}
101+
}

0 commit comments

Comments
 (0)