Skip to content

Commit e895b69

Browse files
committed
Merge 3.2
2 parents 0b724d9 + 4c5a35f commit e895b69

File tree

15 files changed

+195
-52
lines changed

15 files changed

+195
-52
lines changed

CHANGELOG.md

+10
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,16 @@ api_platform:
8181
form: ['multipart/form-data']
8282
```
8383

84+
## v3.2.18
85+
86+
### Bug fixes
87+
88+
* [0073a2a1b](https://github.com/api-platform/core/commit/0073a2a1b752001c9bff666ad350e248b725a80a) fix(serializer): json non-resource intermittent class (HAL & JSON:API) (#6231)
89+
* [3689ae5f4](https://github.com/api-platform/core/commit/3689ae5f47e68e77f6938b71e9e355c13623dbdb) fix(hydra): owl:maxCardinality should be an int (#6235)
90+
* [4b70b7405](https://github.com/api-platform/core/commit/4b70b74054658f9d1c704b56f940ec9742e4482f) fix(jsonschema): generation of non-LD+JSON distinct schema formats (#6236)
91+
* [818b9cd62](https://github.com/api-platform/core/commit/818b9cd625f56c77c9f1fd0286672973d817bb3b) fix(jsonschema): don't skip remaining multiple union types (#6223)
92+
* [874e4d670](https://github.com/api-platform/core/commit/874e4d6707ecdd48b27ed91b0b66ea5e33691e7c) fix(serializer): collection property in an output dto (#6239)
93+
8494
## v3.2.17
8595

8696
### Bug fixes

src/Hal/JsonSchema/SchemaFactory.php

+16-7
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
* @author Kévin Dunglas <[email protected]>
2525
* @author Jachim Coudenys <[email protected]>
2626
*/
27-
final class SchemaFactory implements SchemaFactoryInterface
27+
final class SchemaFactory implements SchemaFactoryInterface, SchemaFactoryAwareInterface
2828
{
2929
private const HREF_PROP = [
3030
'href' => [
@@ -46,7 +46,6 @@ final class SchemaFactory implements SchemaFactoryInterface
4646

4747
public function __construct(private readonly SchemaFactoryInterface $schemaFactory)
4848
{
49-
$this->addDistinctFormat('jsonhal');
5049
if ($this->schemaFactory instanceof SchemaFactoryAwareInterface) {
5150
$this->schemaFactory->setSchemaFactory($this);
5251
}
@@ -79,8 +78,18 @@ public function buildSchema(string $className, string $format = 'jsonhal', strin
7978
$schema['type'] = 'object';
8079
$schema['properties'] = [
8180
'_embedded' => [
82-
'type' => 'array',
83-
'items' => $items,
81+
'anyOf' => [
82+
[
83+
'type' => 'object',
84+
'properties' => [
85+
'item' => [
86+
'type' => 'array',
87+
'items' => $items,
88+
],
89+
],
90+
],
91+
['type' => 'object'],
92+
],
8493
],
8594
'totalItems' => [
8695
'type' => 'integer',
@@ -127,10 +136,10 @@ public function buildSchema(string $className, string $format = 'jsonhal', strin
127136
return $schema;
128137
}
129138

130-
public function addDistinctFormat(string $format): void
139+
public function setSchemaFactory(SchemaFactoryInterface $schemaFactory): void
131140
{
132-
if (method_exists($this->schemaFactory, 'addDistinctFormat')) {
133-
$this->schemaFactory->addDistinctFormat($format);
141+
if ($this->schemaFactory instanceof SchemaFactoryAwareInterface) {
142+
$this->schemaFactory->setSchemaFactory($schemaFactory);
134143
}
135144
}
136145
}

src/Hydra/JsonSchema/SchemaFactory.php

+4-6
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
use ApiPlatform\JsonLd\ContextBuilder;
1717
use ApiPlatform\JsonSchema\Schema;
18-
use ApiPlatform\JsonSchema\SchemaFactory as BaseSchemaFactory;
1918
use ApiPlatform\JsonSchema\SchemaFactoryAwareInterface;
2019
use ApiPlatform\JsonSchema\SchemaFactoryInterface;
2120
use ApiPlatform\Metadata\Operation;
@@ -25,7 +24,7 @@
2524
*
2625
* @author Kévin Dunglas <[email protected]>
2726
*/
28-
final class SchemaFactory implements SchemaFactoryInterface
27+
final class SchemaFactory implements SchemaFactoryInterface, SchemaFactoryAwareInterface
2928
{
3029
private const BASE_PROP = [
3130
'readOnly' => true,
@@ -60,7 +59,6 @@ final class SchemaFactory implements SchemaFactoryInterface
6059

6160
public function __construct(private readonly SchemaFactoryInterface $schemaFactory)
6261
{
63-
$this->addDistinctFormat('jsonld');
6462
if ($this->schemaFactory instanceof SchemaFactoryAwareInterface) {
6563
$this->schemaFactory->setSchemaFactory($this);
6664
}
@@ -184,10 +182,10 @@ public function buildSchema(string $className, string $format = 'jsonld', string
184182
return $schema;
185183
}
186184

187-
public function addDistinctFormat(string $format): void
185+
public function setSchemaFactory(SchemaFactoryInterface $schemaFactory): void
188186
{
189-
if ($this->schemaFactory instanceof BaseSchemaFactory) {
190-
$this->schemaFactory->addDistinctFormat($format);
187+
if ($this->schemaFactory instanceof SchemaFactoryAwareInterface) {
188+
$this->schemaFactory->setSchemaFactory($schemaFactory);
191189
}
192190
}
193191
}

src/JsonSchema/SchemaFactory.php

+2-13
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,13 @@
3636
final class SchemaFactory implements SchemaFactoryInterface, SchemaFactoryAwareInterface
3737
{
3838
use ResourceClassInfoTrait;
39-
private array $distinctFormats = [];
4039
private ?TypeFactoryInterface $typeFactory = null;
4140
private ?SchemaFactoryInterface $schemaFactory = null;
4241
// Edge case where the related resource is not readable (for example: NotExposed) but we have groups to read the whole related object
4342
public const FORCE_SUBSCHEMA = '_api_subschema_force_readable_link';
4443
public const OPENAPI_DEFINITION_NAME = 'openapi_definition_name';
4544

46-
public function __construct(?TypeFactoryInterface $typeFactory, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory, private readonly ?NameConverterInterface $nameConverter = null, ?ResourceClassResolverInterface $resourceClassResolver = null)
45+
public function __construct(?TypeFactoryInterface $typeFactory, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory, private readonly ?NameConverterInterface $nameConverter = null, ?ResourceClassResolverInterface $resourceClassResolver = null, private readonly ?array $distinctFormats = null)
4746
{
4847
if ($typeFactory) {
4948
$this->typeFactory = $typeFactory;
@@ -53,16 +52,6 @@ public function __construct(?TypeFactoryInterface $typeFactory, ResourceMetadata
5352
$this->resourceClassResolver = $resourceClassResolver;
5453
}
5554

56-
/**
57-
* When added to the list, the given format will lead to the creation of a new definition.
58-
*
59-
* @internal
60-
*/
61-
public function addDistinctFormat(string $format): void
62-
{
63-
$this->distinctFormats[$format] = true;
64-
}
65-
6655
/**
6756
* {@inheritdoc}
6857
*/
@@ -267,7 +256,7 @@ private function buildDefinitionName(string $className, string $format = 'json',
267256
$prefix .= '.'.$shortName;
268257
}
269258

270-
if (isset($this->distinctFormats[$format])) {
259+
if ('json' !== $format && ($this->distinctFormats[$format] ?? false)) {
271260
// JSON is the default, and so isn't included in the definition name
272261
$prefix .= '.'.$format;
273262
}

src/Serializer/AbstractItemNormalizer.php

+4
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,10 @@ protected function getAttributeValue(object $object, string $attribute, ?string
769769
}
770770

771771
if ('array' === $type->getBuiltinType()) {
772+
if ($className = ($type->getCollectionValueTypes()[0] ?? null)?->getClassName()) {
773+
$context = $this->createOperationContext($context, $className);
774+
}
775+
772776
$childContext = $this->createChildContext($context, $attribute, $format);
773777
$childContext['output']['gen_id'] = $propertyMetadata->getGenId() ?? true;
774778

src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php

+13-2
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,16 @@ public function load(array $configs, ContainerBuilder $container): void
127127
$patchFormats = $this->getFormats($config['patch_formats']);
128128
$errorFormats = $this->getFormats($config['error_formats']);
129129
$docsFormats = $this->getFormats($config['docs_formats']);
130+
$jsonSchemaFormats = $config['jsonschema_formats'];
131+
132+
if (!$jsonSchemaFormats) {
133+
foreach (array_keys($formats) as $f) {
134+
// Distinct JSON-based formats must have names that start with 'json'
135+
if (str_starts_with($f, 'json')) {
136+
$jsonSchemaFormats[$f] = true;
137+
}
138+
}
139+
}
130140

131141
if (!isset($errorFormats['json'])) {
132142
$errorFormats['json'] = ['application/problem+json', 'application/json'];
@@ -151,7 +161,7 @@ public function load(array $configs, ContainerBuilder $container): void
151161
$docsFormats['jsonopenapi'] = ['application/vnd.openapi+json'];
152162
}
153163

154-
$this->registerCommonConfiguration($container, $config, $loader, $formats, $patchFormats, $errorFormats, $docsFormats);
164+
$this->registerCommonConfiguration($container, $config, $loader, $formats, $patchFormats, $errorFormats, $docsFormats, $jsonSchemaFormats);
155165
$this->registerMetadataConfiguration($container, $config, $loader);
156166
$this->registerOAuthConfiguration($container, $config);
157167
$this->registerOpenApiConfiguration($container, $config, $loader);
@@ -193,7 +203,7 @@ public function load(array $configs, ContainerBuilder $container): void
193203
$this->registerInflectorConfiguration($config);
194204
}
195205

196-
private function registerCommonConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader, array $formats, array $patchFormats, array $errorFormats, array $docsFormats): void
206+
private function registerCommonConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader, array $formats, array $patchFormats, array $errorFormats, array $docsFormats, array $jsonSchemaFormats): void
197207
{
198208
$loader->load('state/state.xml');
199209
$loader->load('symfony/symfony.xml');
@@ -247,6 +257,7 @@ private function registerCommonConfiguration(ContainerBuilder $container, array
247257
$container->setParameter('api_platform.patch_formats', $patchFormats);
248258
$container->setParameter('api_platform.error_formats', $errorFormats);
249259
$container->setParameter('api_platform.docs_formats', $docsFormats);
260+
$container->setParameter('api_platform.jsonschema_formats', $jsonSchemaFormats);
250261
$container->setParameter('api_platform.eager_loading.enabled', $this->isConfigEnabled($container, $config['eager_loading']));
251262
$container->setParameter('api_platform.eager_loading.max_joins', $config['eager_loading']['max_joins']);
252263
$container->setParameter('api_platform.eager_loading.fetch_partial', $config['eager_loading']['fetch_partial']);

src/Symfony/Bundle/DependencyInjection/Configuration.php

+8
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,14 @@ public function getConfigTreeBuilder(): TreeBuilder
177177
'jsonproblem' => ['mime_types' => ['application/problem+json']],
178178
'json' => ['mime_types' => ['application/problem+json', 'application/json']],
179179
]);
180+
$rootNode
181+
->children()
182+
->arrayNode('jsonschema_formats')
183+
->scalarPrototype()->end()
184+
->defaultValue([])
185+
->info('The JSON formats to compute the JSON Schemas for.')
186+
->end()
187+
->end();
180188

181189
$this->addDefaultsSection($rootNode);
182190

src/Symfony/Bundle/Resources/config/json_schema.xml

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
2323
<argument type="service" id="api_platform.name_converter" on-invalid="ignore" />
2424
<argument type="service" id="api_platform.resource_class_resolver" />
25+
<argument on-invalid="ignore">%api_platform.jsonschema_formats%</argument>
2526
</service>
2627
<service id="ApiPlatform\JsonSchema\SchemaFactoryInterface" alias="api_platform.json_schema.schema_factory" />
2728

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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\Tests\Fixtures\TestBundle\ApiResource\Issue6211;
15+
16+
use ApiPlatform\Metadata\Get;
17+
use ApiPlatform\Tests\Fixtures\TestBundle\Dto\ArrayPropertyDto;
18+
19+
#[Get(provider: [ArrayPropertyDtoOperation::class, 'provide'], output: ArrayPropertyDto::class)]
20+
class ArrayPropertyDtoOperation
21+
{
22+
public static function provide(): ArrayPropertyDto
23+
{
24+
$d = new ArrayPropertyDto();
25+
$d->name = 'test';
26+
$c = new ArrayPropertyDto();
27+
$c->name = 'test2';
28+
$d->greetings = [$c];
29+
30+
return $d;
31+
}
32+
}
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 <[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\Tests\Fixtures\TestBundle\Dto;
15+
16+
final class ArrayPropertyDto
17+
{
18+
public string $name = '';
19+
20+
/**
21+
* @var array<ArrayPropertyDto>
22+
*/
23+
public array $greetings = [];
24+
}

tests/Functional/ArrayDtoTest.php

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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\Tests\Functional;
15+
16+
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
17+
18+
final class ArrayDtoTest extends ApiTestCase
19+
{
20+
public function testWithGroupFilter(): void
21+
{
22+
$response = self::createClient()->request('GET', '/array_property_dto_operations');
23+
$this->assertArraySubset(['name' => 'test', 'greetings' => [['name' => 'test2']]], $response->toArray());
24+
}
25+
}

tests/Hal/JsonSchema/SchemaFactoryTest.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ protected function setUp(): void
5353
null,
5454
$resourceMetadataFactory->reveal(),
5555
$propertyNameCollectionFactory->reveal(),
56-
$propertyMetadataFactory->reveal()
56+
$propertyMetadataFactory->reveal(),
57+
null,
58+
null,
59+
['jsonapi' => true, 'jsonhal' => true, 'jsonld' => true],
5760
);
5861

5962
$hydraSchemaFactory = new HydraSchemaFactory($baseSchemaFactory);

tests/Hydra/JsonSchema/SchemaFactoryTest.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ protected function setUp(): void
5454
null,
5555
$resourceMetadataFactoryCollection->reveal(),
5656
$propertyNameCollectionFactory->reveal(),
57-
$propertyMetadataFactory->reveal()
57+
$propertyMetadataFactory->reveal(),
58+
null,
59+
null,
60+
['jsonapi' => true, 'jsonhal' => true, 'jsonld' => true],
5861
);
5962

6063
$this->schemaFactory = new SchemaFactory($baseSchemaFactory);

tests/Symfony/Bundle/DependencyInjection/ConfigurationTest.php

+1
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ private function runDefaultConfigTests(array $doctrineIntegrationsToLoad = ['orm
9898
'jsonld' => ['mime_types' => ['application/ld+json']],
9999
'json' => ['mime_types' => ['application/problem+json', 'application/json']],
100100
],
101+
'jsonschema_formats' => [],
101102
'exception_to_status' => [
102103
ExceptionInterface::class => Response::HTTP_BAD_REQUEST,
103104
InvalidArgumentException::class => Response::HTTP_BAD_REQUEST,

0 commit comments

Comments
 (0)