diff --git a/CHANGELOG.md b/CHANGELOG.md index 2be83b8dcb3..2e65c47102a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 3.0.3 + +* Graphql: add a clearer error message when TwigBundle is disabled but graphQL clients are enabled (#5064) + ## 3.0.2 * Metadata: generate skolem IRI by default, use `genId: false` to disable **BC** diff --git a/src/GraphQl/Action/EntrypointAction.php b/src/GraphQl/Action/EntrypointAction.php index f4c9af93e68..dcdd4a1dfd8 100644 --- a/src/GraphQl/Action/EntrypointAction.php +++ b/src/GraphQl/Action/EntrypointAction.php @@ -34,7 +34,7 @@ final class EntrypointAction { private int $debug; - public function __construct(private readonly SchemaBuilderInterface $schemaBuilder, private readonly ExecutorInterface $executor, private readonly GraphiQlAction $graphiQlAction, private readonly GraphQlPlaygroundAction $graphQlPlaygroundAction, private readonly NormalizerInterface $normalizer, private readonly ErrorHandlerInterface $errorHandler, bool $debug = false, private readonly bool $graphiqlEnabled = false, private readonly bool $graphQlPlaygroundEnabled = false, private readonly ?string $defaultIde = null) + public function __construct(private readonly SchemaBuilderInterface $schemaBuilder, private readonly ExecutorInterface $executor, private readonly ?GraphiQlAction $graphiQlAction, private readonly ?GraphQlPlaygroundAction $graphQlPlaygroundAction, private readonly NormalizerInterface $normalizer, private readonly ErrorHandlerInterface $errorHandler, bool $debug = false, private readonly bool $graphiqlEnabled = false, private readonly bool $graphQlPlaygroundEnabled = false, private readonly ?string $defaultIde = null) { $this->debug = $debug ? DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE : DebugFlag::NONE; } @@ -43,11 +43,11 @@ public function __invoke(Request $request): Response { try { if ($request->isMethod('GET') && 'html' === $request->getRequestFormat()) { - if ('graphiql' === $this->defaultIde && $this->graphiqlEnabled) { + if ('graphiql' === $this->defaultIde && $this->graphiqlEnabled && $this->graphiQlAction) { return ($this->graphiQlAction)($request); } - if ('graphql-playground' === $this->defaultIde && $this->graphQlPlaygroundEnabled) { + if ('graphql-playground' === $this->defaultIde && $this->graphQlPlaygroundEnabled && $this->graphQlPlaygroundAction) { return ($this->graphQlPlaygroundAction)($request); } } diff --git a/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php index fcc023abb68..3b1c8faeb5a 100644 --- a/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php +++ b/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php @@ -53,6 +53,7 @@ use Symfony\Component\Uid\AbstractUid; use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Yaml\Yaml; +use Twig\Environment; /** * The extension of this bundle. @@ -462,9 +463,12 @@ private function registerGraphQlConfiguration(ContainerBuilder $container, array { $enabled = $this->isConfigEnabled($container, $config['graphql']); + $graphiqlEnabled = $enabled && $this->isConfigEnabled($container, $config['graphql']['graphiql']); + $graphqlPlayGroundEnabled = $enabled && $this->isConfigEnabled($container, $config['graphql']['graphql_playground']); + $container->setParameter('api_platform.graphql.enabled', $enabled); - $container->setParameter('api_platform.graphql.graphiql.enabled', $enabled && $this->isConfigEnabled($container, $config['graphql']['graphiql'])); - $container->setParameter('api_platform.graphql.graphql_playground.enabled', $enabled && $this->isConfigEnabled($container, $config['graphql']['graphql_playground'])); + $container->setParameter('api_platform.graphql.graphiql.enabled', $graphiqlEnabled); + $container->setParameter('api_platform.graphql.graphql_playground.enabled', $graphqlPlayGroundEnabled); $container->setParameter('api_platform.graphql.collection.pagination', $config['graphql']['collection']['pagination']); if (!$enabled) { @@ -476,6 +480,15 @@ private function registerGraphQlConfiguration(ContainerBuilder $container, array $loader->load('graphql.xml'); + // @phpstan-ignore-next-line because PHPStan uses the container of the test env cache and in test the parameter kernel.bundles always contains the key TwigBundle + if (!class_exists(Environment::class) || !isset($container->getParameter('kernel.bundles')['TwigBundle'])) { + if ($graphiqlEnabled || $graphqlPlayGroundEnabled) { + throw new RuntimeException(sprintf('GraphiQL and GraphQL Playground interfaces depend on Twig. Please activate TwigBundle for the %s environnement or disable GraphiQL and GraphQL Playground.', $container->getParameter('kernel.environment'))); + } + $container->removeDefinition('api_platform.graphql.action.graphiql'); + $container->removeDefinition('api_platform.graphql.action.graphql_playground'); + } + $container->registerForAutoconfiguration(QueryItemResolverInterface::class) ->addTag('api_platform.graphql.query_resolver'); $container->registerForAutoconfiguration(QueryCollectionResolverInterface::class) diff --git a/src/Symfony/Bundle/Resources/config/graphql.xml b/src/Symfony/Bundle/Resources/config/graphql.xml index debaefb508c..b5a8e4e80e5 100644 --- a/src/Symfony/Bundle/Resources/config/graphql.xml +++ b/src/Symfony/Bundle/Resources/config/graphql.xml @@ -49,8 +49,8 @@ - - + + %kernel.debug% diff --git a/tests/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php index cf89456b6af..006ad7e788d 100644 --- a/tests/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php +++ b/tests/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php @@ -70,7 +70,9 @@ use Prophecy\PhpUnit\ProphecyTrait; use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Bundle\SecurityBundle\SecurityBundle; +use Symfony\Bundle\TwigBundle\TwigBundle; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Uid\AbstractUid; @@ -163,6 +165,7 @@ protected function setUp(): void 'kernel.bundles' => [ 'DoctrineBundle' => DoctrineBundle::class, 'SecurityBundle' => SecurityBundle::class, + 'TwigBundle' => TwigBundle::class, ], 'kernel.bundles_metadata' => [ 'TestBundle' => [ @@ -173,6 +176,7 @@ protected function setUp(): void ], 'kernel.project_dir' => __DIR__.'/../../../Fixtures/app', 'kernel.debug' => false, + 'kernel.environment' => 'test', ]); $this->container = new ContainerBuilder($containerParameterBag); @@ -693,6 +697,37 @@ public function testGraphQlConfiguration(): void $this->assertServiceHasTags('api_platform.graphql.normalizer.runtime_exception', ['serializer.normalizer']); } + public function testRuntimeExceptionIsThrownIfTwigIsNotEnabledButGraphqlClientsAre(): void + { + $config = self::DEFAULT_CONFIG; + $config['api_platform']['graphql']['enabled'] = true; + $this->container->getParameterBag()->set('kernel.bundles', [ + 'DoctrineBundle' => DoctrineBundle::class, + 'SecurityBundle' => SecurityBundle::class, + ]); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('GraphiQL and GraphQL Playground interfaces depend on Twig. Please activate TwigBundle for the test environnement or disable GraphiQL and GraphQL Playground.'); + + (new ApiPlatformExtension())->load($config, $this->container); + } + + public function testGraphqlClientsDefinitionsAreRemovedIfDisabled(): void + { + $config = self::DEFAULT_CONFIG; + $config['api_platform']['graphql']['enabled'] = true; + $config['api_platform']['graphql']['graphiql']['enabled'] = false; + $config['api_platform']['graphql']['graphql_playground']['enabled'] = false; + $this->container->getParameterBag()->set('kernel.bundles', [ + 'DoctrineBundle' => DoctrineBundle::class, + 'SecurityBundle' => SecurityBundle::class, + ]); + + (new ApiPlatformExtension())->load($config, $this->container); + + $this->assertNotContainerHasService('api_platform.graphql.action.graphiql'); + $this->assertNotContainerHasService('api_platform.graphql.action.graphql_playground'); + } + public function testDoctrineOrmConfiguration(): void { $config = self::DEFAULT_CONFIG;