Skip to content

Commit

Permalink
Merge pull request api-platform#6167 from soyuka/merging
Browse files Browse the repository at this point in the history
Merge 3.2
  • Loading branch information
soyuka authored Feb 20, 2024
2 parents 89c9229 + f3874d2 commit 401b658
Show file tree
Hide file tree
Showing 13 changed files with 238 additions and 5 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,24 @@ These namespaces are deprecated:

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

## v3.2.14

### Bug fixes

* [26295392d](https://github.com/api-platform/core/commit/26295392d5e70075b2951d27c633cf29d6fdf542) fix: use normalisation context when none is provided in ApiTestAssertionsTrait (#6157)
* [2999d9ef1](https://github.com/api-platform/core/commit/2999d9ef14416b4cb8728ad713a9edd367df9816) fix: return null instead of exception for GraphQL Query operation (#6118)
* [30f3f353e](https://github.com/api-platform/core/commit/30f3f353e2022ad6ec80733e90f209f326dc3225) fix(openapi): skip requestBody if input is false (#6163)
* [507edba82](https://github.com/api-platform/core/commit/507edba822d80005345794cec1a946f9a7e0c12c) fix(symfony): autoconfiguration on UriVariableTransformerInterface (#6159)
* [643cff2db](https://github.com/api-platform/core/commit/643cff2db8dbab050aa125eb32a347ad37a95e08) fix(symfony): throw metadata exception (#6164)
* [a987469e0](https://github.com/api-platform/core/commit/a987469e09608d91afd4507ec1f6ceacbd8653b2) fix(openapi): method OpenApi::getComponents must always return a Components object (#6158)
* [c08f1e164](https://github.com/api-platform/core/commit/c08f1e1642f4427269a7f684f0b3def34ba4c433) fix(doctrine): test array type existence before using it (#6161)

## v3.2.13

### Bug fixes

* [05713bfc8](https://github.com/api-platform/core/commit/05713bfc8ca4e749d408aaf870a4880e6c8fa74f) fix(hydra): move owl:maxCardinality from JsonSchema to Hydra (#6136)

## v3.2.12

### Bug fixes
Expand Down
6 changes: 5 additions & 1 deletion src/Doctrine/Orm/Filter/SearchFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -369,8 +369,12 @@ protected function createWrapCase(bool $caseSensitive): \Closure
*/
protected function getType(string $doctrineType): string
{
// Remove this test when doctrine/dbal:3 support is removed
if (\defined(Types::class.'::ARRAY') && Types::ARRAY === $doctrineType) {
return 'array';
}

return match ($doctrineType) {
Types::ARRAY => 'array',
Types::BIGINT, Types::INTEGER, Types::SMALLINT => 'int',
Types::BOOLEAN => 'bool',
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,
Expand Down
6 changes: 5 additions & 1 deletion src/GraphQl/State/Provider/ReadProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@

namespace ApiPlatform\GraphQl\State\Provider;

use ApiPlatform\Exception\ItemNotFoundException;
use ApiPlatform\GraphQl\Resolver\Util\IdentifierTrait;
use ApiPlatform\GraphQl\Serializer\ItemNormalizer;
use ApiPlatform\GraphQl\Serializer\SerializerContextBuilderInterface;
use ApiPlatform\GraphQl\Util\ArrayTrait;
use ApiPlatform\Metadata\CollectionOperationInterface;
use ApiPlatform\Metadata\Exception\ItemNotFoundException;
use ApiPlatform\Metadata\GraphQl\Mutation;
use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation;
use ApiPlatform\Metadata\GraphQl\QueryCollection;
Expand Down Expand Up @@ -78,6 +78,10 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
}
}

if (null === $item) {
return $item;
}

if (!\is_object($item)) {
throw new \LogicException('Item from read provider should be a nullable object.');
}
Expand Down
21 changes: 21 additions & 0 deletions src/GraphQl/Tests/State/Provider/ReadProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

use ApiPlatform\GraphQl\Serializer\SerializerContextBuilderInterface;
use ApiPlatform\GraphQl\State\Provider\ReadProvider;
use ApiPlatform\Metadata\Exception\ItemNotFoundException;
use ApiPlatform\Metadata\GraphQl\Query;
use ApiPlatform\Metadata\GraphQl\QueryCollection;
use ApiPlatform\Metadata\IriConverterInterface;
Expand All @@ -36,6 +37,26 @@ public function testProvide(): void
$provider->provide($operation, [], $context);
}

/**
* Tests that provider returns null if resource is not found.
*
* @see https://github.com/api-platform/core/issues/6072
*/
public function testProvideNotExistedResource(): void
{
$context = ['args' => ['id' => '/dummy/1']];
$operation = new Query(class: 'dummy');
$decorated = $this->createMock(ProviderInterface::class);
$iriConverter = $this->createMock(IriConverterInterface::class);
$iriConverter->expects($this->once())->method('getResourceFromIri')->with('/dummy/1');
$iriConverter->method('getResourceFromIri')->willThrowException(new ItemNotFoundException());
$serializerContextBuilder = $this->createMock(SerializerContextBuilderInterface::class);
$provider = new ReadProvider($decorated, $iriConverter, $serializerContextBuilder, '.');
$result = $provider->provide($operation, [], $context);

$this->assertNull($result);
}

public function testProvideCollection(): void
{
$info = $this->createMock(ResolveInfo::class);
Expand Down
5 changes: 4 additions & 1 deletion src/OpenApi/Factory/OpenApiFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,10 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection
'The "openapiContext" option is deprecated, use "openapi" instead.'
);
$openapiOperation = $openapiOperation->withRequestBody(new RequestBody($contextRequestBody['description'] ?? '', new \ArrayObject($contextRequestBody['content']), $contextRequestBody['required'] ?? false));
} elseif (null === $openapiOperation->getRequestBody() && \in_array($method, ['PATCH', 'PUT', 'POST'], true)) {
} elseif (
null === $openapiOperation->getRequestBody() && \in_array($method, ['PATCH', 'PUT', 'POST'], true)
&& !(false === ($input = $operation->getInput()) || (\is_array($input) && null === $input['class']))
) {
$operationInputSchemas = [];
foreach ($requestMimeTypes as $operationFormat) {
$operationInputSchema = $this->jsonSchemaFactory->buildSchema($resourceClass, $operationFormat, Schema::TYPE_INPUT, $operation, $schema, null, $forceSchemaCollection);
Expand Down
4 changes: 3 additions & 1 deletion src/OpenApi/OpenApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ final class OpenApi
public const VERSION = '3.1.0';

private string $openapi = self::VERSION;
private Components $components;

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)
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)
{
$this->components = $components ?? new Components();
}

public function getOpenapi(): string
Expand Down
24 changes: 24 additions & 0 deletions src/OpenApi/Tests/Factory/OpenApiFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ public function testInvoke(): void
),
],
)),
'postDummyItemWithoutInput' => (new Post())->withUriTemplate('/dummyitem/noinput')->withOperation($baseOperation)->withInput(false),
])
);

Expand Down Expand Up @@ -927,5 +928,28 @@ public function testInvoke(): void
]),
]
), $dummyItemPath->getGet());

$emptyRequestBodyPath = $paths->getPath('/dummyitem/noinput');
$this->assertEquals(new Operation(
'postDummyItemWithoutInput',
['Dummy'],
[
'201' => new Response(
'Dummy resource created',
new \ArrayObject([
'application/ld+json' => new MediaType(new \ArrayObject(new \ArrayObject(['$ref' => '#/components/schemas/Dummy.OutputDto']))),
]),
null,
new \ArrayObject(['getDummyItem' => new Model\Link('getDummyItem', new \ArrayObject(['id' => '$response.body#/id']), null, 'This is a dummy')])
),
'400' => new Response('Invalid input'),
'422' => new Response('Unprocessable entity'),
],
'Creates a Dummy resource.',
'Creates a Dummy resource.',
null,
[],
null
), $emptyRequestBodyPath->getPost());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
use ApiPlatform\Hydra\EventListener\AddLinkHeaderListener as HydraAddLinkHeaderListener;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\FilterInterface;
use ApiPlatform\Metadata\UriVariableTransformerInterface;
use ApiPlatform\Metadata\UrlGeneratorInterface;
use ApiPlatform\Metadata\Util\Inflector;
use ApiPlatform\State\ApiResource\Error;
Expand Down Expand Up @@ -177,6 +178,8 @@ public function load(array $configs, ContainerBuilder $container): void
->addTag('api_platform.state_provider');
$container->registerForAutoconfiguration(ProcessorInterface::class)
->addTag('api_platform.state_processor');
$container->registerForAutoconfiguration(UriVariableTransformerInterface::class)
->addTag('api_platform.uri_variables.transformer');

if (!$container->has('api_platform.state.item_provider')) {
$container->setAlias('api_platform.state.item_provider', 'api_platform.state_provider.object');
Expand Down
4 changes: 4 additions & 0 deletions src/Symfony/Bundle/Test/ApiTestAssertionsTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ public static function assertMatchesResourceCollectionJsonSchema(string $resourc
$operation = $operationName ? (new GetCollection())->withName($operationName) : new GetCollection();
}

$serializationContext = $serializationContext ?? $operation->getNormalizationContext();

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

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

$serializationContext = $serializationContext ?? $operation->getNormalizationContext();

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

static::assertMatchesJsonSchema($schema->getArrayCopy());
Expand Down
66 changes: 66 additions & 0 deletions tests/Fixtures/TestBundle/Entity/Issue6146/Issue6146Child.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6146;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;

#[ApiResource(
operations: [
new Get(uriTemplate: 'issue-6146-childs/{id}'),
new GetCollection(uriTemplate: 'issue-6146-childs'),
],
normalizationContext: ['groups' => ['testgroup']],
)]
#[ORM\Entity]
class Issue6146Child
{
#[ORM\Column(type: 'integer')]
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'AUTO')]
private ?int $id = null;

#[ORM\ManyToOne(targetEntity: Issue6146Parent::class, inversedBy: 'childs')]
#[ORM\JoinColumn(nullable: false)]
private Issue6146Parent $parent;

#[ORM\Column(type: 'string')]
#[Groups(['testgroup'])]
private string $foo = 'testtest';

public function getFoo(): string
{
return $this->foo;
}

public function setParent(Issue6146Parent $parent): self
{
$this->parent = $parent;

return $this;
}

public function getParent(): Issue6146Parent
{
return $this->parent;
}

public function getId(): ?int
{
return $this->id;
}
}
57 changes: 57 additions & 0 deletions tests/Fixtures/TestBundle/Entity/Issue6146/Issue6146Parent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6146;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;

#[ApiResource(
operations: [
new Get(uriTemplate: 'issue-6146-parents/{id}'),
new GetCollection(uriTemplate: 'issue-6146-parents'),
],
normalizationContext: ['groups' => ['testgroup']],
)]
#[ORM\Entity]
class Issue6146Parent
{
#[ORM\Column(type: 'integer')]
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'AUTO')]
private ?int $id = null;

#[ORM\OneToMany(mappedBy: 'parent', targetEntity: Issue6146Child::class)]
#[Groups(['testgroup'])]
private Collection $childs;

public function __construct()
{
$this->childs = new ArrayCollection();
}

public function getId(): ?int
{
return $this->id;
}

public function addChild(Issue6146Child $child): void
{
$this->childs->add($child);
}
}
27 changes: 27 additions & 0 deletions tests/Symfony/Bundle/Test/ApiTestCaseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyDtoInputOutput;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6041\NumericValidated;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6146\Issue6146Child;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6146\Issue6146Parent;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\JsonSchemaContextDummy;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\User;
use ApiPlatform\Tests\Fixtures\TestBundle\Model\ResourceInterface;
Expand Down Expand Up @@ -126,6 +128,31 @@ public function testAssertMatchesResourceCollectionJsonSchema(): void
$this->assertMatchesResourceCollectionJsonSchema(ResourceInterface::class);
}

public function testAssertMatchesResourceCollectionJsonSchemaKeepSerializationContext(): void
{
$this->recreateSchema();

/** @var EntityManagerInterface $manager */
$manager = static::getContainer()->get('doctrine')->getManager();

$parent = new Issue6146Parent();
$manager->persist($parent);

$child = new Issue6146Child();
$child->setParent($parent);
$parent->addChild($child);
$manager->persist($child);

$manager->persist($child);
$manager->flush();

self::createClient()->request('GET', "issue-6146-parents/{$parent->getId()}");
$this->assertMatchesResourceItemJsonSchema(Issue6146Parent::class);

self::createClient()->request('GET', '/issue-6146-parents');
$this->assertMatchesResourceCollectionJsonSchema(Issue6146Parent::class);
}

public function testAssertMatchesResourceItemJsonSchema(): void
{
self::createClient()->request('GET', '/resource_interfaces/some-id');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public function testCreate(Constraint $constraint, ApiProperty $propertyMetadata

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

$fields = [
'name' => new Required([
Expand Down

0 comments on commit 401b658

Please sign in to comment.