Skip to content

Commit 401b658

Browse files
authored
Merge pull request api-platform#6167 from soyuka/merging
Merge 3.2
2 parents 89c9229 + f3874d2 commit 401b658

File tree

13 files changed

+238
-5
lines changed

13 files changed

+238
-5
lines changed

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,24 @@ These namespaces are deprecated:
5656

5757
Most of the classes have moved to `ApiPlatform\Metadata`.
5858

59+
## v3.2.14
60+
61+
### Bug fixes
62+
63+
* [26295392d](https://github.com/api-platform/core/commit/26295392d5e70075b2951d27c633cf29d6fdf542) fix: use normalisation context when none is provided in ApiTestAssertionsTrait (#6157)
64+
* [2999d9ef1](https://github.com/api-platform/core/commit/2999d9ef14416b4cb8728ad713a9edd367df9816) fix: return null instead of exception for GraphQL Query operation (#6118)
65+
* [30f3f353e](https://github.com/api-platform/core/commit/30f3f353e2022ad6ec80733e90f209f326dc3225) fix(openapi): skip requestBody if input is false (#6163)
66+
* [507edba82](https://github.com/api-platform/core/commit/507edba822d80005345794cec1a946f9a7e0c12c) fix(symfony): autoconfiguration on UriVariableTransformerInterface (#6159)
67+
* [643cff2db](https://github.com/api-platform/core/commit/643cff2db8dbab050aa125eb32a347ad37a95e08) fix(symfony): throw metadata exception (#6164)
68+
* [a987469e0](https://github.com/api-platform/core/commit/a987469e09608d91afd4507ec1f6ceacbd8653b2) fix(openapi): method OpenApi::getComponents must always return a Components object (#6158)
69+
* [c08f1e164](https://github.com/api-platform/core/commit/c08f1e1642f4427269a7f684f0b3def34ba4c433) fix(doctrine): test array type existence before using it (#6161)
70+
71+
## v3.2.13
72+
73+
### Bug fixes
74+
75+
* [05713bfc8](https://github.com/api-platform/core/commit/05713bfc8ca4e749d408aaf870a4880e6c8fa74f) fix(hydra): move owl:maxCardinality from JsonSchema to Hydra (#6136)
76+
5977
## v3.2.12
6078

6179
### Bug fixes

src/Doctrine/Orm/Filter/SearchFilter.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,8 +369,12 @@ protected function createWrapCase(bool $caseSensitive): \Closure
369369
*/
370370
protected function getType(string $doctrineType): string
371371
{
372+
// Remove this test when doctrine/dbal:3 support is removed
373+
if (\defined(Types::class.'::ARRAY') && Types::ARRAY === $doctrineType) {
374+
return 'array';
375+
}
376+
372377
return match ($doctrineType) {
373-
Types::ARRAY => 'array',
374378
Types::BIGINT, Types::INTEGER, Types::SMALLINT => 'int',
375379
Types::BOOLEAN => 'bool',
376380
Types::DATE_MUTABLE, Types::TIME_MUTABLE, Types::DATETIME_MUTABLE, Types::DATETIMETZ_MUTABLE, Types::DATE_IMMUTABLE, Types::TIME_IMMUTABLE, Types::DATETIME_IMMUTABLE, Types::DATETIMETZ_IMMUTABLE => \DateTimeInterface::class,

src/GraphQl/State/Provider/ReadProvider.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@
1313

1414
namespace ApiPlatform\GraphQl\State\Provider;
1515

16-
use ApiPlatform\Exception\ItemNotFoundException;
1716
use ApiPlatform\GraphQl\Resolver\Util\IdentifierTrait;
1817
use ApiPlatform\GraphQl\Serializer\ItemNormalizer;
1918
use ApiPlatform\GraphQl\Serializer\SerializerContextBuilderInterface;
2019
use ApiPlatform\GraphQl\Util\ArrayTrait;
2120
use ApiPlatform\Metadata\CollectionOperationInterface;
21+
use ApiPlatform\Metadata\Exception\ItemNotFoundException;
2222
use ApiPlatform\Metadata\GraphQl\Mutation;
2323
use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation;
2424
use ApiPlatform\Metadata\GraphQl\QueryCollection;
@@ -78,6 +78,10 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
7878
}
7979
}
8080

81+
if (null === $item) {
82+
return $item;
83+
}
84+
8185
if (!\is_object($item)) {
8286
throw new \LogicException('Item from read provider should be a nullable object.');
8387
}

src/GraphQl/Tests/State/Provider/ReadProviderTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use ApiPlatform\GraphQl\Serializer\SerializerContextBuilderInterface;
1717
use ApiPlatform\GraphQl\State\Provider\ReadProvider;
18+
use ApiPlatform\Metadata\Exception\ItemNotFoundException;
1819
use ApiPlatform\Metadata\GraphQl\Query;
1920
use ApiPlatform\Metadata\GraphQl\QueryCollection;
2021
use ApiPlatform\Metadata\IriConverterInterface;
@@ -36,6 +37,26 @@ public function testProvide(): void
3637
$provider->provide($operation, [], $context);
3738
}
3839

40+
/**
41+
* Tests that provider returns null if resource is not found.
42+
*
43+
* @see https://github.com/api-platform/core/issues/6072
44+
*/
45+
public function testProvideNotExistedResource(): void
46+
{
47+
$context = ['args' => ['id' => '/dummy/1']];
48+
$operation = new Query(class: 'dummy');
49+
$decorated = $this->createMock(ProviderInterface::class);
50+
$iriConverter = $this->createMock(IriConverterInterface::class);
51+
$iriConverter->expects($this->once())->method('getResourceFromIri')->with('/dummy/1');
52+
$iriConverter->method('getResourceFromIri')->willThrowException(new ItemNotFoundException());
53+
$serializerContextBuilder = $this->createMock(SerializerContextBuilderInterface::class);
54+
$provider = new ReadProvider($decorated, $iriConverter, $serializerContextBuilder, '.');
55+
$result = $provider->provide($operation, [], $context);
56+
57+
$this->assertNull($result);
58+
}
59+
3960
public function testProvideCollection(): void
4061
{
4162
$info = $this->createMock(ResolveInfo::class);

src/OpenApi/Factory/OpenApiFactory.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,10 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection
347347
'The "openapiContext" option is deprecated, use "openapi" instead.'
348348
);
349349
$openapiOperation = $openapiOperation->withRequestBody(new RequestBody($contextRequestBody['description'] ?? '', new \ArrayObject($contextRequestBody['content']), $contextRequestBody['required'] ?? false));
350-
} elseif (null === $openapiOperation->getRequestBody() && \in_array($method, ['PATCH', 'PUT', 'POST'], true)) {
350+
} elseif (
351+
null === $openapiOperation->getRequestBody() && \in_array($method, ['PATCH', 'PUT', 'POST'], true)
352+
&& !(false === ($input = $operation->getInput()) || (\is_array($input) && null === $input['class']))
353+
) {
351354
$operationInputSchemas = [];
352355
foreach ($requestMimeTypes as $operationFormat) {
353356
$operationInputSchema = $this->jsonSchemaFactory->buildSchema($resourceClass, $operationFormat, Schema::TYPE_INPUT, $operation, $schema, null, $forceSchemaCollection);

src/OpenApi/OpenApi.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ final class OpenApi
2525
public const VERSION = '3.1.0';
2626

2727
private string $openapi = self::VERSION;
28+
private Components $components;
2829

29-
public function __construct(private Info $info, private array $servers, private Paths $paths, private ?Components $components = null, private array $security = [], private array $tags = [], private $externalDocs = null, private ?string $jsonSchemaDialect = null, private readonly ?\ArrayObject $webhooks = null)
30+
public function __construct(private Info $info, private array $servers, private Paths $paths, ?Components $components = null, private array $security = [], private array $tags = [], private $externalDocs = null, private ?string $jsonSchemaDialect = null, private readonly ?\ArrayObject $webhooks = null)
3031
{
32+
$this->components = $components ?? new Components();
3133
}
3234

3335
public function getOpenapi(): string

src/OpenApi/Tests/Factory/OpenApiFactoryTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ public function testInvoke(): void
239239
),
240240
],
241241
)),
242+
'postDummyItemWithoutInput' => (new Post())->withUriTemplate('/dummyitem/noinput')->withOperation($baseOperation)->withInput(false),
242243
])
243244
);
244245

@@ -927,5 +928,28 @@ public function testInvoke(): void
927928
]),
928929
]
929930
), $dummyItemPath->getGet());
931+
932+
$emptyRequestBodyPath = $paths->getPath('/dummyitem/noinput');
933+
$this->assertEquals(new Operation(
934+
'postDummyItemWithoutInput',
935+
['Dummy'],
936+
[
937+
'201' => new Response(
938+
'Dummy resource created',
939+
new \ArrayObject([
940+
'application/ld+json' => new MediaType(new \ArrayObject(new \ArrayObject(['$ref' => '#/components/schemas/Dummy.OutputDto']))),
941+
]),
942+
null,
943+
new \ArrayObject(['getDummyItem' => new Model\Link('getDummyItem', new \ArrayObject(['id' => '$response.body#/id']), null, 'This is a dummy')])
944+
),
945+
'400' => new Response('Invalid input'),
946+
'422' => new Response('Unprocessable entity'),
947+
],
948+
'Creates a Dummy resource.',
949+
'Creates a Dummy resource.',
950+
null,
951+
[],
952+
null
953+
), $emptyRequestBodyPath->getPost());
930954
}
931955
}

src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
use ApiPlatform\Hydra\EventListener\AddLinkHeaderListener as HydraAddLinkHeaderListener;
3333
use ApiPlatform\Metadata\ApiResource;
3434
use ApiPlatform\Metadata\FilterInterface;
35+
use ApiPlatform\Metadata\UriVariableTransformerInterface;
3536
use ApiPlatform\Metadata\UrlGeneratorInterface;
3637
use ApiPlatform\Metadata\Util\Inflector;
3738
use ApiPlatform\State\ApiResource\Error;
@@ -177,6 +178,8 @@ public function load(array $configs, ContainerBuilder $container): void
177178
->addTag('api_platform.state_provider');
178179
$container->registerForAutoconfiguration(ProcessorInterface::class)
179180
->addTag('api_platform.state_processor');
181+
$container->registerForAutoconfiguration(UriVariableTransformerInterface::class)
182+
->addTag('api_platform.uri_variables.transformer');
180183

181184
if (!$container->has('api_platform.state.item_provider')) {
182185
$container->setAlias('api_platform.state.item_provider', 'api_platform.state_provider.object');

src/Symfony/Bundle/Test/ApiTestAssertionsTrait.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ public static function assertMatchesResourceCollectionJsonSchema(string $resourc
119119
$operation = $operationName ? (new GetCollection())->withName($operationName) : new GetCollection();
120120
}
121121

122+
$serializationContext = $serializationContext ?? $operation->getNormalizationContext();
123+
122124
$schema = $schemaFactory->buildSchema($resourceClass, $format, Schema::TYPE_OUTPUT, $operation, null, ($serializationContext ?? []) + [BackwardCompatibleSchemaFactory::SCHEMA_DRAFT4_VERSION => true]);
123125

124126
static::assertMatchesJsonSchema($schema->getArrayCopy());
@@ -134,6 +136,8 @@ public static function assertMatchesResourceItemJsonSchema(string $resourceClass
134136
$operation = $operationName ? (new Get())->withName($operationName) : new Get();
135137
}
136138

139+
$serializationContext = $serializationContext ?? $operation->getNormalizationContext();
140+
137141
$schema = $schemaFactory->buildSchema($resourceClass, $format, Schema::TYPE_OUTPUT, $operation, null, ($serializationContext ?? []) + [BackwardCompatibleSchemaFactory::SCHEMA_DRAFT4_VERSION => true]);
138142

139143
static::assertMatchesJsonSchema($schema->getArrayCopy());
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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\Entity\Issue6146;
15+
16+
use ApiPlatform\Metadata\ApiResource;
17+
use ApiPlatform\Metadata\Get;
18+
use ApiPlatform\Metadata\GetCollection;
19+
use Doctrine\ORM\Mapping as ORM;
20+
use Symfony\Component\Serializer\Attribute\Groups;
21+
22+
#[ApiResource(
23+
operations: [
24+
new Get(uriTemplate: 'issue-6146-childs/{id}'),
25+
new GetCollection(uriTemplate: 'issue-6146-childs'),
26+
],
27+
normalizationContext: ['groups' => ['testgroup']],
28+
)]
29+
#[ORM\Entity]
30+
class Issue6146Child
31+
{
32+
#[ORM\Column(type: 'integer')]
33+
#[ORM\Id]
34+
#[ORM\GeneratedValue(strategy: 'AUTO')]
35+
private ?int $id = null;
36+
37+
#[ORM\ManyToOne(targetEntity: Issue6146Parent::class, inversedBy: 'childs')]
38+
#[ORM\JoinColumn(nullable: false)]
39+
private Issue6146Parent $parent;
40+
41+
#[ORM\Column(type: 'string')]
42+
#[Groups(['testgroup'])]
43+
private string $foo = 'testtest';
44+
45+
public function getFoo(): string
46+
{
47+
return $this->foo;
48+
}
49+
50+
public function setParent(Issue6146Parent $parent): self
51+
{
52+
$this->parent = $parent;
53+
54+
return $this;
55+
}
56+
57+
public function getParent(): Issue6146Parent
58+
{
59+
return $this->parent;
60+
}
61+
62+
public function getId(): ?int
63+
{
64+
return $this->id;
65+
}
66+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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\Entity\Issue6146;
15+
16+
use ApiPlatform\Metadata\ApiResource;
17+
use ApiPlatform\Metadata\Get;
18+
use ApiPlatform\Metadata\GetCollection;
19+
use Doctrine\Common\Collections\ArrayCollection;
20+
use Doctrine\Common\Collections\Collection;
21+
use Doctrine\ORM\Mapping as ORM;
22+
use Symfony\Component\Serializer\Attribute\Groups;
23+
24+
#[ApiResource(
25+
operations: [
26+
new Get(uriTemplate: 'issue-6146-parents/{id}'),
27+
new GetCollection(uriTemplate: 'issue-6146-parents'),
28+
],
29+
normalizationContext: ['groups' => ['testgroup']],
30+
)]
31+
#[ORM\Entity]
32+
class Issue6146Parent
33+
{
34+
#[ORM\Column(type: 'integer')]
35+
#[ORM\Id]
36+
#[ORM\GeneratedValue(strategy: 'AUTO')]
37+
private ?int $id = null;
38+
39+
#[ORM\OneToMany(mappedBy: 'parent', targetEntity: Issue6146Child::class)]
40+
#[Groups(['testgroup'])]
41+
private Collection $childs;
42+
43+
public function __construct()
44+
{
45+
$this->childs = new ArrayCollection();
46+
}
47+
48+
public function getId(): ?int
49+
{
50+
return $this->id;
51+
}
52+
53+
public function addChild(Issue6146Child $child): void
54+
{
55+
$this->childs->add($child);
56+
}
57+
}

tests/Symfony/Bundle/Test/ApiTestCaseTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy;
1919
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyDtoInputOutput;
2020
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6041\NumericValidated;
21+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6146\Issue6146Child;
22+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6146\Issue6146Parent;
2123
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\JsonSchemaContextDummy;
2224
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\User;
2325
use ApiPlatform\Tests\Fixtures\TestBundle\Model\ResourceInterface;
@@ -126,6 +128,31 @@ public function testAssertMatchesResourceCollectionJsonSchema(): void
126128
$this->assertMatchesResourceCollectionJsonSchema(ResourceInterface::class);
127129
}
128130

131+
public function testAssertMatchesResourceCollectionJsonSchemaKeepSerializationContext(): void
132+
{
133+
$this->recreateSchema();
134+
135+
/** @var EntityManagerInterface $manager */
136+
$manager = static::getContainer()->get('doctrine')->getManager();
137+
138+
$parent = new Issue6146Parent();
139+
$manager->persist($parent);
140+
141+
$child = new Issue6146Child();
142+
$child->setParent($parent);
143+
$parent->addChild($child);
144+
$manager->persist($child);
145+
146+
$manager->persist($child);
147+
$manager->flush();
148+
149+
self::createClient()->request('GET', "issue-6146-parents/{$parent->getId()}");
150+
$this->assertMatchesResourceItemJsonSchema(Issue6146Parent::class);
151+
152+
self::createClient()->request('GET', '/issue-6146-parents');
153+
$this->assertMatchesResourceCollectionJsonSchema(Issue6146Parent::class);
154+
}
155+
129156
public function testAssertMatchesResourceItemJsonSchema(): void
130157
{
131158
self::createClient()->request('GET', '/resource_interfaces/some-id');

tests/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaCollectionRestrictionTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public function testCreate(Constraint $constraint, ApiProperty $propertyMetadata
7979

8080
public static function createProvider(): \Generator
8181
{
82-
yield 'empty' => [new Collection(['fields' => []]), new ApiProperty(), ['type' => 'object', 'properties' => [], 'additionalProperties' => false]];
82+
yield 'empty' => [new Collection([]), new ApiProperty(), ['type' => 'object', 'properties' => [], 'additionalProperties' => false]];
8383

8484
$fields = [
8585
'name' => new Required([

0 commit comments

Comments
 (0)