diff --git a/README.md b/README.md index a39a2d5..06044e4 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,6 @@ Symfony bundle for the `thecodingmachine/graphqlite` package. +Bundle docs: https://graphqlite.thecodingmachine.io/docs/symfony-bundle + See [thecodingmachine/graphqlite](https://github.com/thecodingmachine/graphqlite). diff --git a/composer.json b/composer.json index 59b6100..ade7829 100644 --- a/composer.json +++ b/composer.json @@ -20,22 +20,22 @@ "ext-json": "*", "thecodingmachine/graphqlite" : "^8", "thecodingmachine/graphqlite-symfony-validator-bridge": "^7.1.1", - "symfony/config": "^6.4 || ^7", - "symfony/console": "^6.4 || ^7", - "symfony/framework-bundle": "^6.4 || ^7", - "symfony/validator": "^6.4 || ^7", - "symfony/translation": "^6.4 || ^7", - "symfony/psr-http-message-bridge": "^2.0 || ^7.0", + "symfony/config": "^6.4 || ^7.0 || ^8.0", + "symfony/console": "^6.4 || ^7.0 || ^8.0", + "symfony/framework-bundle": "^6.4 || ^7.0 || ^8.0", + "symfony/validator": "^6.4 || ^7.0 || ^8.0", + "symfony/translation": "^6.4 || ^7.0 || ^8.0", + "symfony/psr-http-message-bridge": "^2.0 || ^7.0 || ^8.0", "nyholm/psr7": "^1.1", "laminas/laminas-diactoros": "^2.2.2 || ^3", "overblog/graphiql-bundle": "^0.2 || ^0.3 || ^1", "thecodingmachine/cache-utils": "^1" }, "require-dev": { - "symfony/security-bundle": "^6.4 || ^7", - "symfony/yaml": "^6.4 || ^7", + "symfony/security-bundle": "^6.4 || ^7.0 || ^8.0", + "symfony/yaml": "^6.4 || ^7.0 || ^8.0", "beberlei/porpaginas": "^1.2 || ^2.0", - "symfony/phpunit-bridge": "^6.4 || ^7", + "symfony/phpunit-bridge": "^6.4 || ^7 || ^8.0", "phpstan/phpstan": "^2", "phpstan/phpstan-symfony": "^2.0", "composer/package-versions-deprecated": "^1.8", diff --git a/config-template/routes/graphqlite.yaml b/config-template/routes/graphqlite.yaml index b366d2d..fa9b36a 100644 --- a/config-template/routes/graphqlite.yaml +++ b/config-template/routes/graphqlite.yaml @@ -1,2 +1,2 @@ graphqlite_bundle: - resource: '@GraphQLiteBundle/Resources/config/routes.xml' + resource: '@GraphQLiteBundle/Resources/config/routes.php' diff --git a/phpstan.neon b/phpstan.neon index 53427c0..544bd75 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -23,6 +23,10 @@ parameters: reportUnmatchedIgnoredErrors: false ignoreErrors: # Wrong return type hint in Symfony's TreeBuilder - - '#Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition::\w+\(\).#' + - message: '#Call to method arrayNode\(\) on an unknown class#' + path: src/DependencyInjection/Configuration.php + - message: '#Cannot call method \w+\(\) on mixed#' + path: src/DependencyInjection/Configuration.php # PhpStan doesn't know bundle resolved config structure and it's pretty complex to describe it - - '#Parameter \#2 \$value of method Symfony\\Component\\DependencyInjection\\Container::setParameter\(\) expects array\|bool\|float\|int\|string\|UnitEnum\|null, mixed given\.#' + - message: '#Parameter \#2 \$value of method Symfony\\Component\\DependencyInjection\\Container::setParameter\(\) expects array\|bool\|float\|int\|string\|UnitEnum\|null, mixed given\.#' + path: src/DependencyInjection/GraphQLiteExtension.php diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 1991328..e331543 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -8,8 +8,10 @@ class Configuration implements ConfigurationInterface { + /** @return TreeBuilder<'array'> */ public function getConfigTreeBuilder(): TreeBuilder { + /** @var TreeBuilder<'array'> $treeBuilder */ $treeBuilder = new TreeBuilder('graphqlite'); $rootNode = $treeBuilder->getRootNode(); diff --git a/src/DependencyInjection/GraphQLiteExtension.php b/src/DependencyInjection/GraphQLiteExtension.php index 7c20e39..82dd202 100644 --- a/src/DependencyInjection/GraphQLiteExtension.php +++ b/src/DependencyInjection/GraphQLiteExtension.php @@ -10,7 +10,7 @@ use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\Extension; -use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use TheCodingMachine\GraphQLite\Mappers\Root\RootTypeMapperFactoryInterface; use function array_map; use function rtrim; @@ -34,7 +34,7 @@ public function load(array $configs, ContainerBuilder $container): void $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); - $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config/container')); + $loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config/container')); if (!isset($config['namespace'])) { $config['namespace'] = []; @@ -92,7 +92,7 @@ function($namespace): string { $container->setParameter('graphqlite.security.maximum_query_depth', $config['security']['maximum_query_depth'] ?? null); $container->setParameter('graphqlite.security.firewall_name', $config['security']['firewall_name'] ?? 'main'); - $loader->load('graphqlite.xml'); + $loader->load('graphqlite.php'); $definition = $container->getDefinition(ServerConfig::class); if (isset($config['debug']) && \is_array($config['debug'])) { diff --git a/src/Resources/config/container/graphqlite.php b/src/Resources/config/container/graphqlite.php new file mode 100644 index 0000000..0797a34 --- /dev/null +++ b/src/Resources/config/container/graphqlite.php @@ -0,0 +1,135 @@ +services(); + + $services->defaults() + ->private() + ->autowire() + ->autoconfigure(); + + $services->set(\TheCodingMachine\GraphQLite\SchemaFactory::class) + ->args( + [ + service('graphqlite.psr16cache'), + service('service_container'), + ], + ) + ->call( + 'setAuthenticationService', + [service(\TheCodingMachine\GraphQLite\Security\AuthenticationServiceInterface::class)], + ) + ->call( + 'setAuthorizationService', + [service(\TheCodingMachine\GraphQLite\Security\AuthorizationServiceInterface::class)], + ); + + $services->set(\TheCodingMachine\GraphQLite\Schema::class) + ->public() + ->factory([service(\TheCodingMachine\GraphQLite\SchemaFactory::class), 'createSchema']); + + $services->set(\TheCodingMachine\GraphQLite\AggregateControllerQueryProviderFactory::class) + ->args( + [ + [], + service_locator([]), + ], + ) + ->tag('graphql.queryprovider_factory'); + + $services->alias(\GraphQL\Type\Schema::class, \TheCodingMachine\GraphQLite\Schema::class); + + $services->set(\TheCodingMachine\GraphQLite\AnnotationReader::class); + + $services->set(\TheCodingMachine\GraphQLite\Bundle\Security\AuthenticationService::class) + ->args([service('security.token_storage')->nullOnInvalid()]); + + $services->alias( + \TheCodingMachine\GraphQLite\Security\AuthenticationServiceInterface::class, + \TheCodingMachine\GraphQLite\Bundle\Security\AuthenticationService::class, + ); + + $services->set(\TheCodingMachine\GraphQLite\Bundle\Security\AuthorizationService::class) + ->args([service('security.authorization_checker')->nullOnInvalid()]); + + $services->alias( + \TheCodingMachine\GraphQLite\Security\AuthorizationServiceInterface::class, + \TheCodingMachine\GraphQLite\Bundle\Security\AuthorizationService::class, + ); + + $services->set(\GraphQL\Server\ServerConfig::class, \TheCodingMachine\GraphQLite\Bundle\Server\ServerConfig::class) + ->call('setSchema', [service(\TheCodingMachine\GraphQLite\Schema::class)]) + ->call( + 'setErrorFormatter', + [ + [ + \TheCodingMachine\GraphQLite\Exceptions\WebonyxErrorHandler::class, + 'errorFormatter', + ], + ], + ) + ->call( + 'setErrorsHandler', + [ + [ + \TheCodingMachine\GraphQLite\Exceptions\WebonyxErrorHandler::class, + 'errorHandler', + ], + ], + ); + + $services->set(\GraphQL\Validator\Rules\DisableIntrospection::class) + ->args(['$enabled' => '%graphqlite.security.disableIntrospection%']); + + $services->set(\GraphQL\Validator\Rules\QueryComplexity::class); + + $services->set(\GraphQL\Validator\Rules\QueryDepth::class); + + $services->set(\TheCodingMachine\GraphQLite\Bundle\Controller\GraphQLiteController::class) + ->public() + ->tag('routing.route_loader'); + + $services->set(\TheCodingMachine\GraphQLite\Bundle\Mappers\RequestParameterMiddleware::class) + ->tag('graphql.parameter_middleware'); + + $services->set(\TheCodingMachine\GraphQLite\Validator\Mappers\Parameters\AssertParameterMiddleware::class) + ->args([service('validator.validator_factory')]) + ->tag('graphql.parameter_middleware'); + + $services->set(\TheCodingMachine\GraphQLite\Bundle\Controller\GraphQL\LoginController::class) + ->public() + ->args(['$firewallName' => '%graphqlite.security.firewall_name%']); + + $services->set(\TheCodingMachine\GraphQLite\Bundle\Controller\GraphQL\MeController::class) + ->public(); + + $services->set(\TheCodingMachine\GraphQLite\Bundle\Types\SymfonyUserInterfaceType::class) + ->public(); + + $services->set(\TheCodingMachine\GraphQLite\Mappers\StaticClassListTypeMapperFactory::class) + ->args([[]]) + ->tag('graphql.type_mapper_factory'); + + $services->set('graphqlite.phpfilescache', \Symfony\Component\Cache\Adapter\PhpFilesAdapter::class) + ->args( + [ + 'graphqlite', + 0, + '%kernel.cache_dir%', + ], + ); + + $services->set('graphqlite.apcucache', \Symfony\Component\Cache\Adapter\ApcuAdapter::class) + ->args(['graphqlite']); + + $services->set('graphqlite.psr16cache', \Symfony\Component\Cache\Psr16Cache::class) + ->args([service('graphqlite.cache')]); + + $services->set('graphqlite.cacheclearer', \Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer::class) + ->args([[service('graphqlite.cache')]]) + ->tag('kernel.cache_clearer'); + + $services->set(\TheCodingMachine\GraphQLite\Bundle\Command\DumpSchemaCommand::class); +}; diff --git a/src/Resources/config/container/graphqlite.xml b/src/Resources/config/container/graphqlite.xml deleted file mode 100644 index 984caa7..0000000 --- a/src/Resources/config/container/graphqlite.xml +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \TheCodingMachine\GraphQLite\Exceptions\WebonyxErrorHandler - errorFormatter - - - - - \TheCodingMachine\GraphQLite\Exceptions\WebonyxErrorHandler - errorHandler - - - - - - %graphqlite.security.disableIntrospection% - - - - - - - - - - - - - - - - - - - - - %graphqlite.security.firewall_name% - - - - - - - - - - - - - - graphqlite - 0 - %kernel.cache_dir% - - - - graphqlite - - - - - - - - - - - - - - - - diff --git a/src/Resources/config/routes.php b/src/Resources/config/routes.php new file mode 100644 index 0000000..8f4a722 --- /dev/null +++ b/src/Resources/config/routes.php @@ -0,0 +1,11 @@ +import( + \sprintf('%s::loadRoutes', GraphQLiteController::class), + 'service', + ); +}; diff --git a/tests/Fixtures/Entities/Contact.php b/tests/Fixtures/Entities/Contact.php index fc90f60..2677537 100644 --- a/tests/Fixtures/Entities/Contact.php +++ b/tests/Fixtures/Entities/Contact.php @@ -30,11 +30,11 @@ public function getName(): string #[Field] public function injectService( #[Autowire] - TestGraphqlController $testService = null, + ?TestGraphqlController $testService = null, #[Autowire(identifier: 'someService')] - stdClass $someService = null, + ?stdClass $someService = null, #[Autowire(identifier: 'someAlias')] - stdClass $someAlias = null, + ?stdClass $someAlias = null, ): string { if (!$testService instanceof TestGraphqlController || $someService === null || $someAlias === null) { return 'KO'; @@ -52,7 +52,7 @@ public function injectServicePrefetch($prefetchData): string public function prefetchData( iterable $iterable, #[Autowire(identifier: 'someOtherService')] - stdClass $someOtherService = null, + ?stdClass $someOtherService = null, ) { if ($someOtherService === null) { return 'KO'; diff --git a/tests/FunctionalTest.php b/tests/FunctionalTest.php index 07848bc..b5ecc32 100644 --- a/tests/FunctionalTest.php +++ b/tests/FunctionalTest.php @@ -374,6 +374,7 @@ public function testNoLoginNoSessionQuery(): void $result = json_decode($response->getContent(), true); + $this->assertIsArray($result); $this->assertArrayHasKey('errors', $result); $this->assertSame('Cannot query field "login" on type "Mutation".', $result['errors'][0]['message']); } @@ -508,8 +509,11 @@ public function testWithIntrospection(): void $response = $kernel->handle($request); $result = json_decode($response->getContent(), true); + $this->assertIsArray($result); + $this->assertArrayHasKey('data', $result); $data = $result['data']; + $this->assertIsArray($result); $this->assertArrayHasKey('__schema', $data); } diff --git a/tests/GraphQLiteTestingKernel.php b/tests/GraphQLiteTestingKernel.php index c92ad95..16e358c 100644 --- a/tests/GraphQLiteTestingKernel.php +++ b/tests/GraphQLiteTestingKernel.php @@ -13,6 +13,7 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; use TheCodingMachine\GraphQLite\Bundle\GraphQLiteBundle; use Symfony\Component\Security\Core\User\InMemoryUser; use function class_exists; @@ -196,10 +197,9 @@ public function configureContainer(ContainerBuilder $c, LoaderInterface $loader) $loader->load($confDir.'/{services}_'.$this->environment.self::CONFIG_EXTS, 'glob'); } - // Note: typing is disabled because using different classes in Symfony 4 and 5 - protected function configureRoutes(/*RoutingConfigurator*/ $routes) + protected function configureRoutes(RoutingConfigurator $routes): void { - $routes->import(__DIR__.'/../src/Resources/config/routes.xml'); + $routes->import(__DIR__.'/../src/Resources/config/routes.php'); } public function getCacheDir(): string