Skip to content

Commit 523fc1d

Browse files
committed
tests
1 parent ce60e04 commit 523fc1d

File tree

70 files changed

+1306
-261
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+1306
-261
lines changed

features/mongodb/filters.feature

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Feature: Filters on collections
1010
When I send a "GET" request to "/dummies?relatedDummy.thirdLevel.badFourthLevel.level=4"
1111
Then the response status code should be 500
1212
And the response should be in JSON
13-
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
13+
And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8"
1414
And the JSON node "@context" should be equal to "/contexts/Error"
1515
And the JSON node "@type" should be equal to "hydra:Error"
1616
And the JSON node "hydra:title" should be equal to "An error occurred"
@@ -21,7 +21,7 @@ Feature: Filters on collections
2121
When I send a "GET" request to "/dummies?relatedDummy.thirdLevel.fourthLevel.badThirdLevel.level=3"
2222
Then the response status code should be 500
2323
And the response should be in JSON
24-
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
24+
And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8"
2525
And the JSON node "@context" should be equal to "/contexts/Error"
2626
And the JSON node "@type" should be equal to "hydra:Error"
2727
And the JSON node "hydra:title" should be equal to "An error occurred"

src/Action/ExceptionAction.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
* @author Baptiste Meyer <[email protected]>
3131
* @author Kévin Dunglas <[email protected]>
3232
*
33-
* @deprecated
33+
* @deprecated since API Platform 3 and Error resource is used {@see ApiPlatform\Symfony\EventListener\ErrorListener}
3434
*/
3535
final class ExceptionAction
3636
{

src/Doctrine/Common/State/PersistProcessor.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,14 @@ public function process(mixed $data, Operation $operation, array $uriVariables =
4545
return $data;
4646
}
4747

48-
$request = $context['request'] ?? null;
49-
5048
// PUT: reset the existing object managed by Doctrine and merge data sent by the user in it
5149
// This custom logic is needed because EntityManager::merge() has been deprecated and UPSERT isn't supported:
5250
// https://github.com/doctrine/orm/issues/8461#issuecomment-1250233555
5351
if ($operation instanceof HttpOperation && 'PUT' === $operation->getMethod() && ($operation->getExtraProperties()['standard_put'] ?? false)) {
5452
\assert(method_exists($manager, 'getReference'));
5553
// TODO: the call to getReference is most likely to fail with complex identifiers
5654
$newData = $data;
57-
if ($previousData = $context['previous_data'] ?? $request?->attributes->get('previous_data')) {
55+
if ($previousData = $context['previous_data']) {
5856
$newData = 1 === \count($uriVariables) ? $manager->getReference($class, current($uriVariables)) : clone $previousData;
5957
}
6058

src/Documentation/Action/DocumentationAction.php

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@
1717
use ApiPlatform\Documentation\DocumentationInterface;
1818
use ApiPlatform\Metadata\Get;
1919
use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
20+
use ApiPlatform\Metadata\Util\ContentNegotiationTrait;
2021
use ApiPlatform\OpenApi\Factory\OpenApiFactoryInterface;
2122
use ApiPlatform\OpenApi\OpenApi;
2223
use ApiPlatform\OpenApi\Serializer\ApiGatewayNormalizer;
2324
use ApiPlatform\State\ProcessorInterface;
2425
use ApiPlatform\State\ProviderInterface;
25-
use ApiPlatform\Util\ContentNegotiationTrait;
2626
use Negotiation\Negotiator;
2727
use Symfony\Component\HttpFoundation\Request;
28+
use Symfony\Component\HttpFoundation\Response;
2829

2930
/**
3031
* Generates the API documentation.
@@ -49,45 +50,59 @@ public function __construct(
4950
}
5051

5152
/**
52-
* @return DocumentationInterface|OpenApi
53+
* @return DocumentationInterface|OpenApi|Response
5354
*/
5455
public function __invoke(Request $request = null)
5556
{
56-
$context = [];
57-
if (null !== $request) {
58-
$isGateway = $request->query->getBoolean(ApiGatewayNormalizer::API_GATEWAY);
59-
$context['api_gateway'] = $isGateway;
60-
$context['base_url'] = $request->getBaseUrl();
61-
$request->attributes->set('_api_normalization_context', $request->attributes->get('_api_normalization_context', []) + $context);
62-
$format = $this->getRequestFormat($request, ['json' => ['application/json'], 'jsonld' => ['application/ld+json'], 'html' => ['text/html']]);
63-
64-
if ('html' === $format || 'json' === $format && null !== $this->openApiFactory) {
65-
if ($this->provider && $this->processor) {
66-
$context['request'] = $request;
67-
$operation = new Get(class: OpenApi::class, provider: fn () => $this->openApiFactory->__invoke($context), normalizationContext: [ApiGatewayNormalizer::API_GATEWAY => $isGateway]);
68-
if ('html' === $format) {
69-
$operation = $operation->withProcessor('api_platform.swagger_ui.processor')->withWrite(true);
70-
}
71-
72-
$body = $this->provider->provide($operation, [], $context);
73-
74-
return $this->processor->process($body, $operation, [], $context);
75-
}
76-
77-
return $this->openApiFactory->__invoke($context);
57+
if (null === $request) {
58+
return new Documentation($this->resourceNameCollectionFactory->create(), $this->title, $this->description, $this->version);
59+
}
60+
61+
$context = ['api_gateway' => $request->query->getBoolean(ApiGatewayNormalizer::API_GATEWAY), 'base_url' => $request->getBaseUrl()];
62+
$request->attributes->set('_api_normalization_context', $request->attributes->get('_api_normalization_context', []) + $context);
63+
$format = $this->getRequestFormat($request, ['json' => ['application/json'], 'jsonld' => ['application/ld+json'], 'html' => ['text/html']]);
64+
65+
if (null !== $this->openApiFactory && ('html' === $format || 'json' === $format)) {
66+
return $this->getOpenApiDocumentation($context, $format, $request);
67+
}
68+
69+
return $this->getHydraDocumentation($context, $request);
70+
}
71+
72+
/**
73+
* @param array<string,mixed> $context
74+
*/
75+
private function getOpenApiDocumentation(array $context, string $format, Request $request): OpenApi|Response
76+
{
77+
if ($this->provider && $this->processor) {
78+
$context['request'] = $request;
79+
$operation = new Get(class: OpenApi::class, provider: fn () => $this->openApiFactory->__invoke($context), normalizationContext: [ApiGatewayNormalizer::API_GATEWAY => $context['api_gateway'] ?? null]);
80+
if ('html' === $format) {
81+
$operation = $operation->withProcessor('api_platform.swagger_ui.processor')->withWrite(true);
7882
}
83+
84+
return $this->processor->process($this->provider->provide($operation, [], $context), $operation, [], $context);
7985
}
8086

87+
return $this->openApiFactory->__invoke($context);
88+
}
89+
90+
/**
91+
* TODO: the logic behind the Hydra Documentation is done in a ApiPlatform\Hydra\Serializer\DocumentationNormalizer.
92+
* We should transform this to a provider, it'd improve performances also by a bit.
93+
*
94+
* @param array<string,mixed> $context
95+
*/
96+
private function getHydraDocumentation(array $context, Request $request): DocumentationInterface|Response
97+
{
8198
if ($this->provider && $this->processor) {
8299
$context['request'] = $request;
83100
$operation = new Get(
84101
class: Documentation::class,
85-
provider: fn () => new Documentation($this->resourceNameCollectionFactory->create(), $this->title, $this->description, $this->version),
86-
normalizationContext: [ApiGatewayNormalizer::API_GATEWAY => $isGateway ?? false]
102+
provider: fn () => new Documentation($this->resourceNameCollectionFactory->create(), $this->title, $this->description, $this->version)
87103
);
88-
$body = $this->provider->provide($operation, [], $context);
89104

90-
return $this->processor->process($body, $operation, [], $context);
105+
return $this->processor->process($this->provider->provide($operation, [], $context), $operation, [], $context);
91106
}
92107

93108
return new Documentation($this->resourceNameCollectionFactory->create(), $this->title, $this->description, $this->version);

src/Elasticsearch/Serializer/NameConverter/InnerFieldsNameConverter.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@
1818
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
1919

2020
/**
21-
* Converts inner fields with a decorated name converter.
21+
* Converts inner fields with a inner name converter.
2222
*
2323
* @experimental
2424
*
2525
* @author Baptiste Meyer <[email protected]>
2626
*/
2727
final class InnerFieldsNameConverter implements AdvancedNameConverterInterface
2828
{
29-
public function __construct(private readonly NameConverterInterface $decorated = new CamelCaseToSnakeCaseNameConverter())
29+
public function __construct(private readonly NameConverterInterface $inner = new CamelCaseToSnakeCaseNameConverter())
3030
{
3131
}
3232

@@ -51,7 +51,7 @@ private function convertInnerFields(string $propertyName, bool $normalization, s
5151
$convertedProperties = [];
5252

5353
foreach (explode('.', $propertyName) as $decomposedProperty) {
54-
$convertedProperties[] = $this->decorated->{$normalization ? 'normalize' : 'denormalize'}($decomposedProperty, $class, $format, $context);
54+
$convertedProperties[] = $this->inner->{$normalization ? 'normalize' : 'denormalize'}($decomposedProperty, $class, $format, $context);
5555
}
5656

5757
return implode('.', $convertedProperties);

src/Elasticsearch/Tests/Serializer/NameConverter/InnerFieldsNameConverterTest.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,22 @@ public function testConstruct(): void
3232

3333
public function testNormalize(): void
3434
{
35-
$decoratedProphecy = $this->prophesize(AdvancedNameConverterInterface::class);
36-
$decoratedProphecy->normalize('fooBar', null, null, [])->willReturn('foo_bar')->shouldBeCalled();
37-
$decoratedProphecy->normalize('bazQux', null, null, [])->willReturn('baz_qux')->shouldBeCalled();
35+
$innerProphecy = $this->prophesize(AdvancedNameConverterInterface::class);
36+
$innerProphecy->normalize('fooBar', null, null, [])->willReturn('foo_bar')->shouldBeCalled();
37+
$innerProphecy->normalize('bazQux', null, null, [])->willReturn('baz_qux')->shouldBeCalled();
3838

39-
$innerFieldsNameConverter = new InnerFieldsNameConverter($decoratedProphecy->reveal());
39+
$innerFieldsNameConverter = new InnerFieldsNameConverter($innerProphecy->reveal());
4040

4141
self::assertSame('foo_bar.baz_qux', $innerFieldsNameConverter->normalize('fooBar.bazQux'));
4242
}
4343

4444
public function testDenormalize(): void
4545
{
46-
$decoratedProphecy = $this->prophesize(AdvancedNameConverterInterface::class);
47-
$decoratedProphecy->denormalize('foo_bar', null, null, [])->willReturn('fooBar')->shouldBeCalled();
48-
$decoratedProphecy->denormalize('baz_qux', null, null, [])->willReturn('bazQux')->shouldBeCalled();
46+
$innerProphecy = $this->prophesize(AdvancedNameConverterInterface::class);
47+
$innerProphecy->denormalize('foo_bar', null, null, [])->willReturn('fooBar')->shouldBeCalled();
48+
$innerProphecy->denormalize('baz_qux', null, null, [])->willReturn('bazQux')->shouldBeCalled();
4949

50-
$innerFieldsNameConverter = new InnerFieldsNameConverter($decoratedProphecy->reveal());
50+
$innerFieldsNameConverter = new InnerFieldsNameConverter($innerProphecy->reveal());
5151

5252
self::assertSame('fooBar.bazQux', $innerFieldsNameConverter->denormalize('foo_bar.baz_qux'));
5353
}

src/GraphQl/Action/EntrypointAction.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
use ApiPlatform\GraphQl\Error\ErrorHandlerInterface;
1717
use ApiPlatform\GraphQl\ExecutorInterface;
1818
use ApiPlatform\GraphQl\Type\SchemaBuilderInterface;
19-
use ApiPlatform\Util\ContentNegotiationTrait;
19+
use ApiPlatform\Metadata\Util\ContentNegotiationTrait;
2020
use GraphQL\Error\DebugFlag;
2121
use GraphQL\Error\Error;
2222
use GraphQL\Executor\ExecutionResult;

src/GraphQl/State/Processor/NormalizeProcessor.php

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,8 @@ public function process(mixed $data, Operation $operation, array $uriVariables =
5656
*/
5757
private function getData(mixed $itemOrCollection, GraphQlOperation $operation, array $uriVariables = [], array $context = []): ?array
5858
{
59-
$isCollection = $operation instanceof CollectionOperationInterface;
60-
$isMutation = $operation instanceof Mutation;
61-
$isSubscription = $operation instanceof Subscription;
62-
$isDelete = $operation instanceof DeleteOperationInterface;
63-
$shortName = $operation->getShortName();
64-
6559
if (!($operation->canSerialize() ?? true)) {
66-
if ($isCollection) {
60+
if ($operation instanceof CollectionOperationInterface) {
6761
if ($this->pagination->isGraphQlEnabled($operation, $context)) {
6862
return 'cursor' === $this->pagination->getGraphQlPaginationType($operation) ?
6963
$this->getDefaultCursorBasedPaginatedData() :
@@ -73,11 +67,11 @@ private function getData(mixed $itemOrCollection, GraphQlOperation $operation, a
7367
return [];
7468
}
7569

76-
if ($isMutation) {
70+
if ($operation instanceof Mutation) {
7771
return $this->getDefaultMutationData($context);
7872
}
7973

80-
if ($isSubscription) {
74+
if ($operation instanceof Subscription) {
8175
return $this->getDefaultSubscriptionData($context);
8276
}
8377

@@ -87,15 +81,15 @@ private function getData(mixed $itemOrCollection, GraphQlOperation $operation, a
8781
$normalizationContext = $this->serializerContextBuilder->create($operation->getClass(), $operation, $context, normalization: true);
8882

8983
$data = null;
90-
if (!$isCollection) {
91-
if ($isMutation && $isDelete) {
84+
if (!$operation instanceof CollectionOperationInterface) {
85+
if ($operation instanceof Mutation && $operation instanceof DeleteOperationInterface) {
9286
$data = ['id' => $this->getIdentifierFromOperation($operation, $context['args'] ?? [])];
9387
} else {
9488
$data = $this->normalizer->normalize($itemOrCollection, ItemNormalizer::FORMAT, $normalizationContext);
9589
}
9690
}
9791

98-
if ($isCollection && is_iterable($itemOrCollection)) {
92+
if ($operation instanceof CollectionOperationInterface && is_iterable($itemOrCollection)) {
9993
if (!$this->pagination->isGraphQlEnabled($operation, $context)) {
10094
$data = [];
10195
foreach ($itemOrCollection as $index => $object) {
@@ -112,8 +106,10 @@ private function getData(mixed $itemOrCollection, GraphQlOperation $operation, a
112106
throw new \UnexpectedValueException('Expected serialized data to be a nullable array.');
113107
}
114108

109+
$isMutation = $operation instanceof Mutation;
110+
$isSubscription = $operation instanceof Subscription;
115111
if ($isMutation || $isSubscription) {
116-
$wrapFieldName = lcfirst($shortName);
112+
$wrapFieldName = lcfirst($operation->getShortName());
117113

118114
return [$wrapFieldName => $data] + ($isMutation ? $this->getDefaultMutationData($context) : $this->getDefaultSubscriptionData($context));
119115
}

src/GraphQl/State/Processor/SubscriptionProcessor.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@
2525
*/
2626
final class SubscriptionProcessor implements ProcessorInterface
2727
{
28-
public function __construct(private readonly ProcessorInterface $inner, private readonly SubscriptionManagerInterface $subscriptionManager, private readonly ?MercureSubscriptionIriGeneratorInterface $mercureSubscriptionIriGenerator)
28+
public function __construct(private readonly ProcessorInterface $decorated, private readonly SubscriptionManagerInterface $subscriptionManager, private readonly ?MercureSubscriptionIriGeneratorInterface $mercureSubscriptionIriGenerator)
2929
{
3030
}
3131

3232
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])
3333
{
34-
$data = $this->inner->process($data, $operation, $uriVariables, $context);
34+
$data = $this->decorated->process($data, $operation, $uriVariables, $context);
3535
if (!$operation instanceof GraphQlOperation || !($mercure = $operation->getMercure())) {
3636
return $data;
3737
}

src/GraphQl/State/Provider/DenormalizeProvider.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@
2727
final class DenormalizeProvider implements ProviderInterface
2828
{
2929
/**
30-
* @param ProviderInterface<object> $inner
30+
* @param ProviderInterface<object> $decorated
3131
*/
32-
public function __construct(private readonly ProviderInterface $inner, private readonly DenormalizerInterface $denormalizer, private readonly SerializerContextBuilderInterface $serializerContextBuilder)
32+
public function __construct(private readonly ProviderInterface $decorated, private readonly DenormalizerInterface $denormalizer, private readonly SerializerContextBuilderInterface $serializerContextBuilder)
3333
{
3434
}
3535

3636
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
3737
{
38-
$data = $this->inner->provide($operation, $uriVariables, $context);
38+
$data = $this->decorated->provide($operation, $uriVariables, $context);
3939

4040
if (!($operation->canDeserialize() ?? true) || (!$operation instanceof Mutation)) {
4141
return $data;

src/GraphQl/State/Provider/ReadProvider.php

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,20 @@
1313

1414
namespace ApiPlatform\GraphQl\State\Provider;
1515

16-
use ApiPlatform\Api\IriConverterInterface;
1716
use ApiPlatform\Exception\ItemNotFoundException;
1817
use ApiPlatform\GraphQl\Resolver\Util\IdentifierTrait;
1918
use ApiPlatform\GraphQl\Serializer\ItemNormalizer;
2019
use ApiPlatform\GraphQl\Serializer\SerializerContextBuilderInterface;
20+
use ApiPlatform\GraphQl\Util\ArrayTrait;
2121
use ApiPlatform\Metadata\CollectionOperationInterface;
2222
use ApiPlatform\Metadata\GraphQl\Mutation;
2323
use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation;
2424
use ApiPlatform\Metadata\GraphQl\QueryCollection;
2525
use ApiPlatform\Metadata\GraphQl\Subscription;
26+
use ApiPlatform\Metadata\IriConverterInterface;
2627
use ApiPlatform\Metadata\Operation;
2728
use ApiPlatform\Metadata\Util\ClassInfoTrait;
2829
use ApiPlatform\State\ProviderInterface;
29-
use ApiPlatform\Util\ArrayTrait;
3030
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
3131

3232
/**
@@ -38,12 +38,8 @@ final class ReadProvider implements ProviderInterface
3838
use ClassInfoTrait;
3939
use IdentifierTrait;
4040

41-
public function __construct(
42-
private readonly ProviderInterface $provider,
43-
private readonly IriConverterInterface $iriConverter,
44-
private readonly ?SerializerContextBuilderInterface $serializerContextBuilder,
45-
private readonly string $nestingSeparator
46-
) {
41+
public function __construct(private readonly ProviderInterface $provider, private readonly IriConverterInterface $iriConverter, private readonly ?SerializerContextBuilderInterface $serializerContextBuilder, private readonly string $nestingSeparator)
42+
{
4743
}
4844

4945
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null

src/GraphQl/State/Provider/ResolverProvider.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ final class ResolverProvider implements ProviderInterface
2727
{
2828
use ClassInfoTrait;
2929

30-
public function __construct(private readonly ProviderInterface $inner, private readonly ContainerInterface $queryResolverLocator)
30+
public function __construct(private readonly ProviderInterface $decorated, private readonly ContainerInterface $queryResolverLocator)
3131
{
3232
}
3333

3434
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
3535
{
36-
$item = $this->inner->provide($operation, $uriVariables, $context);
36+
$item = $this->decorated->provide($operation, $uriVariables, $context);
3737

3838
if (!$operation instanceof GraphQlOperation || null === ($queryResolverId = $operation->getResolver())) {
3939
return $item;

0 commit comments

Comments
 (0)