diff --git a/src/Metadata/Resource/Factory/MetadataCollectionFactoryTrait.php b/src/Metadata/Resource/Factory/MetadataCollectionFactoryTrait.php index 7561858e799..6e6212f13e2 100644 --- a/src/Metadata/Resource/Factory/MetadataCollectionFactoryTrait.php +++ b/src/Metadata/Resource/Factory/MetadataCollectionFactoryTrait.php @@ -99,6 +99,9 @@ private function buildResourceOperations(array $metadataCollection, string $reso } if ($metadata instanceof GraphQlOperation) { + if (-1 === $index) { + $resources[++$index] = $this->getResourceWithDefaults($resourceClass, $shortName, new ApiResource()); + } [$key, $operation] = $this->getOperationWithDefaults($resources[$index], $metadata); $graphQlOperations = $resources[$index]->getGraphQlOperations(); $graphQlOperations[$key] = $operation; diff --git a/src/Metadata/Resource/Factory/NotExposedOperationResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/NotExposedOperationResourceMetadataCollectionFactory.php index 5e06fa23f96..6976894085f 100644 --- a/src/Metadata/Resource/Factory/NotExposedOperationResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/NotExposedOperationResourceMetadataCollectionFactory.php @@ -73,7 +73,7 @@ public function create(string $resourceClass): ResourceMetadataCollection [$key, $operation] = $this->getOperationWithDefaults($resource, new NotExposed(), true, ['uriTemplate', 'uriVariables']); // @phpstan-ignore-line $resource is defined if count > 0 if (!$this->linkFactory->createLinksFromIdentifiers($operation)) { - $operation = $operation->withUriTemplate(self::$skolemUriTemplate); + $operation = $operation->withUriTemplate(self::$skolemUriTemplate)->withUriVariables([]); } $operations->add($key, $operation)->sort(); // @phpstan-ignore-line $operation exists diff --git a/src/Metadata/Tests/Resource/Factory/NotExposedOperationResourceMetadataCollectionFactoryTest.php b/src/Metadata/Tests/Resource/Factory/NotExposedOperationResourceMetadataCollectionFactoryTest.php index 90f729f1a8c..7efcab55bb9 100644 --- a/src/Metadata/Tests/Resource/Factory/NotExposedOperationResourceMetadataCollectionFactoryTest.php +++ b/src/Metadata/Tests/Resource/Factory/NotExposedOperationResourceMetadataCollectionFactoryTest.php @@ -232,7 +232,7 @@ class: AttributeResource::class types: ['https://schema.org/Book'], operations: [ '_api_AttributeResource_get_collection' => new GetCollection(controller: 'api_platform.action.placeholder', shortName: 'AttributeResource', class: AttributeResource::class), - '_api_AttributeResource_get' => new NotExposed(uriTemplate: '/.well-known/genid/{id}', controller: 'api_platform.action.not_exposed', shortName: 'AttributeResource', class: AttributeResource::class, output: false, read: false, extraProperties: ['generated_operation' => true], types: ['https://schema.org/Book']), + '_api_AttributeResource_get' => new NotExposed(uriTemplate: '/.well-known/genid/{id}', uriVariables: [], controller: 'api_platform.action.not_exposed', shortName: 'AttributeResource', class: AttributeResource::class, output: false, read: false, extraProperties: ['generated_operation' => true], types: ['https://schema.org/Book']), ], class: AttributeResource::class ), diff --git a/tests/Fixtures/TestBundle/ApiResource/Issue3975/ActionSimulation.php b/tests/Fixtures/TestBundle/ApiResource/Issue3975/ActionSimulation.php new file mode 100644 index 00000000000..6ba628d324d --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/Issue3975/ActionSimulation.php @@ -0,0 +1,36 @@ + + * + * 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\ApiResource\Issue3975; + +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\GraphQl\Query; + +#[ApiResource( + operations: [], + graphQlOperations: [ + new Query( + resolver: ActionSimulationResolver::class, + args: [ + 'actionId' => ['type' => 'String!'], + 'structureEntityIds' => ['type' => '[String!]'], + ], + read: false, + name: 'get' + ), + ] +)] +class ActionSimulation +{ + public string $simulation = '0'; +} diff --git a/tests/Fixtures/TestBundle/ApiResource/Issue3975/ActionSimulationResolver.php b/tests/Fixtures/TestBundle/ApiResource/Issue3975/ActionSimulationResolver.php new file mode 100644 index 00000000000..1e1ec2b5481 --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/Issue3975/ActionSimulationResolver.php @@ -0,0 +1,27 @@ + + * + * 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\ApiResource\Issue3975; + +use ApiPlatform\GraphQl\Resolver\QueryItemResolverInterface; + +class ActionSimulationResolver implements QueryItemResolverInterface +{ + public function __invoke(?object $item, array $context): object + { + $simulation = new ActionSimulation(); + $simulation->simulation = 'test'; + + return $simulation; + } +} diff --git a/tests/Fixtures/app/config/config_common.yml b/tests/Fixtures/app/config/config_common.yml index c89718fadcd..8bf91cb28b5 100644 --- a/tests/Fixtures/app/config/config_common.yml +++ b/tests/Fixtures/app/config/config_common.yml @@ -280,6 +280,10 @@ services: tags: - name: 'messenger.message_handler' + ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue3975\ActionSimulationResolver: + tags: + - name: 'api_platform.graphql.resolver' + app.graphql.query_resolver.dummy_custom_item: class: 'ApiPlatform\Tests\Fixtures\TestBundle\GraphQl\Resolver\DummyCustomQueryItemResolver' tags: diff --git a/tests/Functional/GraphQl/Issue3975Test.php b/tests/Functional/GraphQl/Issue3975Test.php new file mode 100644 index 00000000000..2e6a99c90ee --- /dev/null +++ b/tests/Functional/GraphQl/Issue3975Test.php @@ -0,0 +1,69 @@ + + * + * 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\Functional\GraphQl; + +use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; +use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue3975\ActionSimulation; +use ApiPlatform\Tests\SetupClassResourcesTrait; + +final class Issue3975Test extends ApiTestCase +{ + use SetupClassResourcesTrait; + + /** + * @return class-string[] + */ + public static function getResources(): array + { + return [ActionSimulation::class]; + } + + public function testGraphQlOnlyQueryWithProvider(): void + { + $response = self::createClient()->request('POST', '/graphql', ['json' => [ + 'query' => <<<'GRAPHQL' +{ + getActionSimulation(actionId: "abc123", structureEntityIds: ["s1", "s2"]) { + simulation + } +} +GRAPHQL, + ]]); + + $this->assertResponseIsSuccessful(); + $json = $response->toArray(false); + $this->assertArrayNotHasKey('errors', $json, isset($json['errors']) ? json_encode($json['errors']) : ''); + $this->assertEquals('test', $json['data']['getActionSimulation']['simulation']); + } + + public function testGraphQlOnlyQueryWithId(): void + { + $response = self::createClient()->request('POST', '/graphql', ['json' => [ + 'query' => <<<'GRAPHQL' +{ + getActionSimulation(actionId: "abc123") { + id + simulation + } +} +GRAPHQL, + ]]); + + $this->assertResponseIsSuccessful(); + $json = $response->toArray(false); + $this->assertArrayNotHasKey('errors', $json, isset($json['errors']) ? json_encode($json['errors']) : ''); + $this->assertNotNull($json['data']['getActionSimulation']['id']); + $this->assertEquals('test', $json['data']['getActionSimulation']['simulation']); + } +}