From d18b689681b96597257762dd8f615df3605a0b45 Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Thu, 16 Feb 2017 17:16:40 +0100 Subject: [PATCH 1/2] Add php definition supports --- Command/DebugCommand.php | 93 ++++++++ Command/GraphQLDumpSchemaCommand.php | 4 +- Config/Parser/ParserInterface.php | 26 +++ Config/Parser/XmlParser.php | 49 +++++ Config/Parser/YmlParser.php | 46 ++++ Definition/Resolver/AliasedInterface.php | 25 +++ .../Resolver/MutationInterface.php | 8 +- Definition/Resolver/ResolverInterface.php | 16 ++ Definition/Type/GeneratedTypeInterface.php | 16 ++ .../Compiler/AutoMappingPass.php | 205 ++++++++++++++++++ .../{TypesPass.php => ConfigTypesPass.php} | 26 ++- .../Compiler/TaggedServiceMappingPass.php | 41 +++- DependencyInjection/Configuration.php | 15 +- .../OverblogGraphQLExtension.php | 13 +- .../OverblogGraphQLTypesExtension.php | 107 +++------ .../Relay}/Mutation/MutationFieldResolver.php | 16 +- .../Relay}/Node/GlobalIdFieldResolver.php | 17 +- GraphQL/Relay/Node/NodeFieldResolver.php | 31 +++ .../PluralIdentifyingRootFieldResolver.php | 16 +- OverblogGraphQLBundle.php | 17 +- .../BackwardConnectionArgsDefinition.php | 2 +- Relay/Connection/ConnectionArgsDefinition.php | 2 +- Relay/Connection/ConnectionDefinition.php | 2 +- .../ForwardConnectionArgsDefinition.php | 2 +- Relay/Mutation/InputDefinition.php | 2 +- Relay/Mutation/MutationFieldDefinition.php | 6 +- Relay/Mutation/PayloadDefinition.php | 2 +- Relay/Node/GlobalIdFieldDefinition.php | 6 +- Relay/Node/NodeDefinition.php | 2 +- Relay/Node/NodeFieldDefinition.php | 6 +- .../PluralIdentifyingRootFieldDefinition.php | 7 +- Resolver/AbstractProxyResolver.php | 2 +- Resolver/TypeResolver.php | 24 +- Resources/config/graphql_resolvers.yml | 24 -- Resources/config/graphql_types.yml | 5 + Resources/config/services.yml | 1 + Resources/doc/definitions/index.md | 1 + Resources/doc/definitions/mutation.md | 2 +- Resources/doc/definitions/resolver.md | 51 +++++ .../doc/definitions/type-system/index.md | 73 ++++++- Resources/skeleton/TypeSystem.php.skeleton | 11 +- .../Compiler/ResolverTestService.php | 2 +- .../Exception/ExampleException.php | 2 +- .../HelloWord/Mutation/CalcMutation.php | 31 +++ .../GraphQL/HelloWord/Type/MutationType.php | 41 ++++ .../App/GraphQL/HelloWord/Type/QueryType.php | 41 ++++ .../App/IsolatedResolver/EchoResolver.php | 26 +++ .../SimpleMutationWithThunkFieldsMutation.php | 2 +- .../Mutation/SimplePromiseMutation.php | 2 +- .../Resolver/ConnectionResolver.php | 2 +- .../{app => App}/Resolver/GlobalResolver.php | 2 +- .../{app => App}/Resolver/NodeResolver.php | 8 +- .../{app => App}/Resolver/PluralResolver.php | 2 +- .../{app/AppKernel.php => App/TestKernel.php} | 19 +- .../{app => App}/config/access/config.yml | 0 .../config/access/mapping/access.types.yml | 0 .../App/config/autoMapping/config.yml | 13 ++ .../Functional/{app => App}/config/config.yml | 1 + .../{app => App}/config/connection/config.yml | 0 .../connection/mapping/connection.types.yml | 0 .../config/connection/services.yml | 2 +- .../config/customScalar/config.yml | 0 .../customScalar/mapping/Query.types.yml | 0 .../{app => App}/config/definition/config.yml | 0 .../definition/mapping/deprecated.types.yml | 0 .../{app => App}/config/exception/config.yml | 0 .../exception/mapping/exception.types.yml | 0 .../config/exception/services.yml | 2 +- .../{app => App}/config/global/config.yml | 2 +- .../config/global/mapping/global.types.yml | 0 .../{app => App}/config/mutation/config.yml | 0 .../config/mutation/mapping/Inputs.types.yml | 0 .../mutation/mapping/Payloads.types.yml | 0 .../mutation/mapping/RootMutation.types.yml | 0 .../{app => App}/config/mutation/services.yml | 4 +- .../{app => App}/config/node/config.yml | 2 +- .../config/node/mapping/Node.types.yml | 0 .../config/node/mapping/Photo.types.yml | 0 .../config/node/mapping/Query.types.yml | 0 .../config/node/mapping/User.types.yml | 0 .../{app => App}/config/plural/config.yml | 2 +- .../config/plural/mapping/Query.types.xml | 0 .../config/plural/mapping/User.types.xml | 0 .../{app => App}/config/public/config.yml | 0 .../config/public/mapping/public.types.yml | 0 .../config/queryComplexity/config.yml | 0 .../connectionWithComplexity.types.yml | 0 .../{app => App}/config/routing.yml | 0 .../{app => App}/config/security.yml | 0 .../Functional/AutoMapping/HelloWordTest.php | 38 ++++ Tests/Functional/BootTest.php | 2 +- Tests/Functional/Command/DebugCommandTest.php | 96 ++++++++ .../Command/GraphDumpSchemaCommandTest.php | 4 +- .../Command/fixtures/debug-mutation.txt | 15 ++ .../Command/fixtures/debug-resolver.txt | 19 ++ .../Command/fixtures/debug-type.txt | 37 ++++ .../Command/{ => fixtures}/schema.graphqls | 0 .../Command/{ => fixtures}/schema.json | 0 Tests/Functional/Exception/ExceptionTest.php | 2 +- .../Relay/Connection/ConnectionTest.php | 2 +- .../Relay/Mutation/MutationTest.php | 2 +- Tests/Functional/Relay/Node/GlobalTest.php | 2 +- Tests/Functional/Relay/Node/NodeTest.php | 2 +- Tests/Functional/Relay/Node/PluralTest.php | 2 +- Tests/Functional/Security/AccessTest.php | 2 +- Tests/Functional/TestCase.php | 18 +- Tests/Functional/Type/CustomScalarTest.php | 2 +- Tests/Functional/Type/DefinitionTest.php | 2 +- Tests/Relay/Node/NodeFieldDefinitionTest.php | 3 +- ...uralIdentifyingRootFieldDefinitionTest.php | 3 +- composer.json | 29 +-- phpunit.xml.dist | 2 +- 112 files changed, 1254 insertions(+), 258 deletions(-) create mode 100644 Command/DebugCommand.php create mode 100644 Config/Parser/ParserInterface.php create mode 100644 Config/Parser/XmlParser.php create mode 100644 Config/Parser/YmlParser.php create mode 100644 Definition/Resolver/AliasedInterface.php rename Relay/Node/NodeFieldResolver.php => Definition/Resolver/MutationInterface.php (53%) create mode 100644 Definition/Resolver/ResolverInterface.php create mode 100644 Definition/Type/GeneratedTypeInterface.php create mode 100644 DependencyInjection/Compiler/AutoMappingPass.php rename DependencyInjection/Compiler/{TypesPass.php => ConfigTypesPass.php} (60%) rename {Relay => GraphQL/Relay}/Mutation/MutationFieldResolver.php (65%) rename {Relay => GraphQL/Relay}/Node/GlobalIdFieldResolver.php (50%) create mode 100644 GraphQL/Relay/Node/NodeFieldResolver.php rename {Relay => GraphQL/Relay}/Node/PluralIdentifyingRootFieldResolver.php (59%) delete mode 100644 Resources/config/graphql_resolvers.yml create mode 100644 Resources/doc/definitions/resolver.md rename Tests/Functional/{app => App}/Exception/ExampleException.php (86%) create mode 100644 Tests/Functional/App/GraphQL/HelloWord/Mutation/CalcMutation.php create mode 100644 Tests/Functional/App/GraphQL/HelloWord/Type/MutationType.php create mode 100644 Tests/Functional/App/GraphQL/HelloWord/Type/QueryType.php create mode 100644 Tests/Functional/App/IsolatedResolver/EchoResolver.php rename Tests/Functional/{app => App}/Mutation/SimpleMutationWithThunkFieldsMutation.php (92%) rename Tests/Functional/{app => App}/Mutation/SimplePromiseMutation.php (90%) rename Tests/Functional/{app => App}/Resolver/ConnectionResolver.php (97%) rename Tests/Functional/{app => App}/Resolver/GlobalResolver.php (97%) rename Tests/Functional/{app => App}/Resolver/NodeResolver.php (86%) rename Tests/Functional/{app => App}/Resolver/PluralResolver.php (90%) rename Tests/Functional/{app/AppKernel.php => App/TestKernel.php} (76%) rename Tests/Functional/{app => App}/config/access/config.yml (100%) rename Tests/Functional/{app => App}/config/access/mapping/access.types.yml (100%) create mode 100644 Tests/Functional/App/config/autoMapping/config.yml rename Tests/Functional/{app => App}/config/config.yml (92%) rename Tests/Functional/{app => App}/config/connection/config.yml (100%) rename Tests/Functional/{app => App}/config/connection/mapping/connection.types.yml (100%) rename Tests/Functional/{app => App}/config/connection/services.yml (89%) rename Tests/Functional/{app => App}/config/customScalar/config.yml (100%) rename Tests/Functional/{app => App}/config/customScalar/mapping/Query.types.yml (100%) rename Tests/Functional/{app => App}/config/definition/config.yml (100%) rename Tests/Functional/{app => App}/config/definition/mapping/deprecated.types.yml (100%) rename Tests/Functional/{app => App}/config/exception/config.yml (100%) rename Tests/Functional/{app => App}/config/exception/mapping/exception.types.yml (100%) rename Tests/Functional/{app => App}/config/exception/services.yml (71%) rename Tests/Functional/{app => App}/config/global/config.yml (89%) rename Tests/Functional/{app => App}/config/global/mapping/global.types.yml (100%) rename Tests/Functional/{app => App}/config/mutation/config.yml (100%) rename Tests/Functional/{app => App}/config/mutation/mapping/Inputs.types.yml (100%) rename Tests/Functional/{app => App}/config/mutation/mapping/Payloads.types.yml (100%) rename Tests/Functional/{app => App}/config/mutation/mapping/RootMutation.types.yml (100%) rename Tests/Functional/{app => App}/config/mutation/services.yml (80%) rename Tests/Functional/{app => App}/config/node/config.yml (93%) rename Tests/Functional/{app => App}/config/node/mapping/Node.types.yml (100%) rename Tests/Functional/{app => App}/config/node/mapping/Photo.types.yml (100%) rename Tests/Functional/{app => App}/config/node/mapping/Query.types.yml (100%) rename Tests/Functional/{app => App}/config/node/mapping/User.types.yml (100%) rename Tests/Functional/{app => App}/config/plural/config.yml (90%) rename Tests/Functional/{app => App}/config/plural/mapping/Query.types.xml (100%) rename Tests/Functional/{app => App}/config/plural/mapping/User.types.xml (100%) rename Tests/Functional/{app => App}/config/public/config.yml (100%) rename Tests/Functional/{app => App}/config/public/mapping/public.types.yml (100%) rename Tests/Functional/{app => App}/config/queryComplexity/config.yml (100%) rename Tests/Functional/{app => App}/config/queryComplexity/mapping/connectionWithComplexity.types.yml (100%) rename Tests/Functional/{app => App}/config/routing.yml (100%) rename Tests/Functional/{app => App}/config/security.yml (100%) create mode 100644 Tests/Functional/AutoMapping/HelloWordTest.php create mode 100644 Tests/Functional/Command/DebugCommandTest.php create mode 100644 Tests/Functional/Command/fixtures/debug-mutation.txt create mode 100644 Tests/Functional/Command/fixtures/debug-resolver.txt create mode 100644 Tests/Functional/Command/fixtures/debug-type.txt rename Tests/Functional/Command/{ => fixtures}/schema.graphqls (100%) rename Tests/Functional/Command/{ => fixtures}/schema.json (100%) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php new file mode 100644 index 000000000..f937425db --- /dev/null +++ b/Command/DebugCommand.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\Command; + +use Overblog\GraphQLBundle\Resolver\ResolverInterface; +use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class DebugCommand extends ContainerAwareCommand +{ + private static $categories = ['type', 'mutation', 'resolver']; + + protected function configure() + { + $this + ->setName('graphql:debug') + ->setAliases(['debug:graphql']) + ->addOption( + 'category', + null, + InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, + sprintf('filter by a category (%s).', implode(', ', self::$categories)) + ) + ->setDescription('Display current GraphQL services (types, resolvers and mutations)'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $categoriesOption = $input->getOption('category'); + $categoriesOption = is_array($categoriesOption) ? $categoriesOption : [$categoriesOption]; + $notAllowed = array_diff($categoriesOption, self::$categories); + if (!empty($notAllowed)) { + throw new \InvalidArgumentException(sprintf('Invalid category (%s)', implode(',', $notAllowed))); + } + + $categories = empty($categoriesOption) ? self::$categories : $categoriesOption; + + $io = new SymfonyStyle($input, $output); + $tableHeaders = ['id', 'aliases']; + foreach ($categories as $category) { + $io->title(sprintf('GraphQL %ss Services', ucfirst($category))); + /** @var ResolverInterface $resolver */ + $resolver = $this->getContainer()->get(sprintf('overblog_graphql.%s_resolver', $category)); + $solutions = $this->retrieveSolutions($resolver); + $this->renderTable($tableHeaders, $solutions, $io); + } + } + + private function renderTable(array $tableHeaders, array $solutions, SymfonyStyle $io) + { + $tableRows = []; + foreach ($solutions as $id => &$options) { + ksort($options['aliases']); + $tableRows[] = [$id, implode("\n", $options['aliases'])]; + } + $io->table($tableHeaders, $tableRows); + $io->write("\n\n"); + } + + private function retrieveSolutions(ResolverInterface $resolver) + { + $data = []; + foreach ($resolver->getSolutions() as $alias => $solution) { + $options = $resolver->getSolutionOptions($alias); + + $id = $options['id']; + if (!isset($data[$id]['aliases'])) { + $data[$id]['aliases'] = []; + } + $data[$id]['aliases'][] = $options['alias'].(isset($options['method']) ? ' (method: '.$options['method'].')' : ''); + } + ksort($data); + + return $data; + } + + public static function getCategories() + { + return self::$categories; + } +} diff --git a/Command/GraphQLDumpSchemaCommand.php b/Command/GraphQLDumpSchemaCommand.php index 17dc4d5fd..b78b8da88 100644 --- a/Command/GraphQLDumpSchemaCommand.php +++ b/Command/GraphQLDumpSchemaCommand.php @@ -51,7 +51,7 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output) { - $output = new SymfonyStyle($input, $output); + $io = new SymfonyStyle($input, $output); $format = strtolower($input->getOption('format')); $schemaName = $input->getOption('schema'); @@ -62,7 +62,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $content = $this->createFileContent($requestExecutor, $format, $schemaName); file_put_contents($file, $content); - $output->success(sprintf('GraphQL schema "%s" was successfully dumped.', realpath($file))); + $io->success(sprintf('GraphQL schema "%s" was successfully dumped.', realpath($file))); } private function createFileContent(Executor $requestExecutor, $format, $schemaName) diff --git a/Config/Parser/ParserInterface.php b/Config/Parser/ParserInterface.php new file mode 100644 index 000000000..8c21b1cda --- /dev/null +++ b/Config/Parser/ParserInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\Config\Parser; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\Finder\SplFileInfo; + +interface ParserInterface +{ + /** + * @param SplFileInfo $file + * @param ContainerBuilder $container + * + * @return array + */ + public static function parse(SplFileInfo $file, ContainerBuilder $container); +} diff --git a/Config/Parser/XmlParser.php b/Config/Parser/XmlParser.php new file mode 100644 index 000000000..9c2024c4e --- /dev/null +++ b/Config/Parser/XmlParser.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\Config\Parser; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Util\XmlUtils; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\Finder\SplFileInfo; + +class XmlParser implements ParserInterface +{ + /* + * @param SplFileInfo $file + * @param ContainerBuilder $container + * + * @return array + */ + public static function parse(SplFileInfo $file, ContainerBuilder $container) + { + $typesConfig = []; + + try { + //@todo fix xml validateSchema + $xml = XmlUtils::loadFile($file->getRealPath()); + foreach ($xml->documentElement->childNodes as $node) { + if (!$node instanceof \DOMElement) { + continue; + } + $values = XmlUtils::convertDomElementToArray($node); + $typesConfig = array_merge($typesConfig, $values); + } + $container->addResource(new FileResource($file->getRealPath())); + } catch (\InvalidArgumentException $e) { + throw new InvalidArgumentException(sprintf('Unable to parse file "%s".', $file), $e->getCode(), $e); + } + + return $typesConfig; + } +} diff --git a/Config/Parser/YmlParser.php b/Config/Parser/YmlParser.php new file mode 100644 index 000000000..23b2e13e9 --- /dev/null +++ b/Config/Parser/YmlParser.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\Config\Parser; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\Finder\SplFileInfo; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Parser as YamlParser; + +class YmlParser implements ParserInterface +{ + private static $yamlParser; + + /** + * @param SplFileInfo $file + * @param ContainerBuilder $container + * + * @return array + */ + public static function parse(SplFileInfo $file, ContainerBuilder $container) + { + if (null === self::$yamlParser) { + self::$yamlParser = new YamlParser(); + } + + try { + $typesConfig = self::$yamlParser->parse($file->getContents()); + $container->addResource(new FileResource($file->getRealPath())); + } catch (ParseException $e) { + throw new InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML.', $file), 0, $e); + } + + return $typesConfig; + } +} diff --git a/Definition/Resolver/AliasedInterface.php b/Definition/Resolver/AliasedInterface.php new file mode 100644 index 000000000..7cef4c8b0 --- /dev/null +++ b/Definition/Resolver/AliasedInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\Definition\Resolver; + +interface AliasedInterface +{ + /** + * Returns methods aliases. + * + * For instance: + * array('myMethod' => 'myAlias') + * + * @return array + */ + public static function getAliases(); +} diff --git a/Relay/Node/NodeFieldResolver.php b/Definition/Resolver/MutationInterface.php similarity index 53% rename from Relay/Node/NodeFieldResolver.php rename to Definition/Resolver/MutationInterface.php index 6bb70041f..cbfc12819 100644 --- a/Relay/Node/NodeFieldResolver.php +++ b/Definition/Resolver/MutationInterface.php @@ -9,12 +9,8 @@ * file that was distributed with this source code. */ -namespace Overblog\GraphQLBundle\Relay\Node; +namespace Overblog\GraphQLBundle\Definition\Resolver; -class NodeFieldResolver +interface MutationInterface { - public function resolve($args, $context, $info, \Closure $idFetcherCallback) - { - return $idFetcherCallback($args['id'], $context, $info); - } } diff --git a/Definition/Resolver/ResolverInterface.php b/Definition/Resolver/ResolverInterface.php new file mode 100644 index 000000000..c6fd828d7 --- /dev/null +++ b/Definition/Resolver/ResolverInterface.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\Definition\Resolver; + +interface ResolverInterface +{ +} diff --git a/Definition/Type/GeneratedTypeInterface.php b/Definition/Type/GeneratedTypeInterface.php new file mode 100644 index 000000000..916a43ea8 --- /dev/null +++ b/Definition/Type/GeneratedTypeInterface.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\Definition\Type; + +interface GeneratedTypeInterface +{ +} diff --git a/DependencyInjection/Compiler/AutoMappingPass.php b/DependencyInjection/Compiler/AutoMappingPass.php new file mode 100644 index 000000000..8a39c2aec --- /dev/null +++ b/DependencyInjection/Compiler/AutoMappingPass.php @@ -0,0 +1,205 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\DependencyInjection\Compiler; + +use GraphQL\Type\Definition\Type; +use Overblog\GraphQLBundle\Definition\Resolver\AliasedInterface; +use Overblog\GraphQLBundle\Definition\Resolver\MutationInterface; +use Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface; +use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Finder\Finder; + +class AutoMappingPass implements CompilerPassInterface +{ + private static $serviceSubclassTagMapping = [ + MutationInterface::class => 'overblog_graphql.mutation', + ResolverInterface::class => 'overblog_graphql.resolver', + Type::class => 'overblog_graphql.type', + ]; + + public function process(ContainerBuilder $container) + { + $enabled = $container->getParameter('overblog_graphql.auto_mapping.enabled'); + // enabled auto mapping for all bundles and custom dirs ? + if ($enabled) { + $directories = $container->getParameter('overblog_graphql.auto_mapping.directories'); + $bundles = $container->getParameter('kernel.bundles'); + $directories = array_merge( + array_map( + function ($class) { + $bundle = new \ReflectionClass($class); + $bundleDir = dirname($bundle->getFileName()); + + return $bundleDir.'/GraphQL'; + }, + $bundles + ), + $directories + ); + // add app dir + if ($container->hasParameter('kernel.root_dir')) { + $directories[] = $container->getParameter('kernel.root_dir').'/GraphQL'; + } + } else { + // enabled auto mapping only for this bundle + $directories = [__DIR__.'/../../GraphQL']; + } + $directoryList = []; + + foreach ($directories as $directory) { + list($reflectionClasses, $directories) = $this->reflectionClassesFromDirectory($directory); + $directoryList = array_merge($directoryList, $directories); + $this->addServicesDefinitions($container, $reflectionClasses); + } + + foreach ($directoryList as $directory => $v) { + $directory = realpath($directory); + $container->addResource(new DirectoryResource($directory, '/\.php$/')); + } + } + + /** + * @param ContainerBuilder $container + * @param \ReflectionClass[] $reflectionClasses + */ + private function addServicesDefinitions(ContainerBuilder $container, array $reflectionClasses) + { + foreach ($reflectionClasses as $reflectionClass) { + $this->addServiceDefinition($container, $reflectionClass); + } + } + + private function addServiceDefinition(ContainerBuilder $container, \ReflectionClass $reflectionClass) + { + $className = $reflectionClass->getName(); + $definition = $container->setDefinition($className, new Definition($className)); + $definition->setPublic(false); + $definition->setAutowired(true); + if (is_subclass_of($definition->getClass(), ContainerAwareInterface::class)) { + $definition->addMethodCall('setContainer', [new Reference('service_container')]); + } + $this->addDefinitionTags($definition, $reflectionClass); + } + + private function addDefinitionTags(Definition $definition, \ReflectionClass $reflectionClass) + { + $className = $definition->getClass(); + + foreach (self::$serviceSubclassTagMapping as $subclass => $tagName) { + if (!$reflectionClass->isSubclassOf($subclass)) { + continue; + } + + if (Type::class !== $subclass) { + $publicReflectionMethods = $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC); + $isAliased = $reflectionClass->implementsInterface(AliasedInterface::class); + foreach ($publicReflectionMethods as $publicReflectionMethod) { + if ('__construct' === $publicReflectionMethod->name || $isAliased && 'getAliases' === $publicReflectionMethod->name) { + continue; + } + $definition->addTag($tagName, ['method' => $publicReflectionMethod->name]); + } + if ($isAliased) { + $this->addDefinitionTagsFromAliasesMethod($definition, $className, $tagName, true); + } + } else { + $definition->addTag($tagName); + $this->addDefinitionTagsFromAliasesMethod($definition, $className, $tagName, false); + } + } + } + + private function addDefinitionTagsFromAliasesMethod(Definition $definition, $className, $tagName, $withMethod) + { + // from getAliases + if (!is_callable([$className, 'getAliases'])) { + return; + } + $aliases = call_user_func([$className, 'getAliases']); + + foreach ($aliases as $key => $alias) { + $definition->addTag($tagName, $withMethod ? ['alias' => $alias, 'method' => $key] : ['alias' => $alias]); + } + } + + private function subclass($class) + { + $interfaces = array_keys(self::$serviceSubclassTagMapping); + + foreach ($interfaces as $interface) { + if (is_a($class, $interface, true)) { + return $interface; + } + } + + return false; + } + + /** + * Gets the classes reflection of class in the given directory. + * + * @param string $directory + * + * @return array + */ + private function reflectionClassesFromDirectory($directory) + { + $classes = []; + $directoryList = []; + $includedFiles = []; + $reflectionClasses = []; + + $finder = new Finder(); + try { + $finder->in($directory)->files()->name('*.php'); + } catch (\InvalidArgumentException $e) { + return [$reflectionClasses, $directoryList]; + } + + foreach ($finder as $file) { + $directoryList[$file->getPath()] = true; + $sourceFile = $file->getRealpath(); + if (!preg_match('(^phar:)i', $sourceFile)) { + $sourceFile = realpath($sourceFile); + } + + require_once $sourceFile; + $includedFiles[$sourceFile] = true; + } + + $declared = get_declared_classes(); + foreach ($declared as $className) { + $subclass = $this->subclass($className); + if (false === $subclass) { + continue; + } + $reflectionClass = new \ReflectionClass($className); + $reflectionClasses[$className] = $reflectionClass; + $sourceFile = $reflectionClass->getFileName(); + + if ($reflectionClass->isAbstract()) { + continue; + } + + if (isset($includedFiles[$sourceFile])) { + $classes[$className] = true; + } + } + + return [array_intersect_key($reflectionClasses, $classes), $directoryList]; + } +} diff --git a/DependencyInjection/Compiler/TypesPass.php b/DependencyInjection/Compiler/ConfigTypesPass.php similarity index 60% rename from DependencyInjection/Compiler/TypesPass.php rename to DependencyInjection/Compiler/ConfigTypesPass.php index c56d3eee7..156d1adf8 100644 --- a/DependencyInjection/Compiler/TypesPass.php +++ b/DependencyInjection/Compiler/ConfigTypesPass.php @@ -14,26 +14,28 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\ExpressionLanguage\Expression; -class TypesPass implements CompilerPassInterface +class ConfigTypesPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { $config = $container->getParameter('overblog_graphql_types.config'); - $classes = array_keys($container->get('overblog_graphql.cache_compiler')->compile($this->processConfig($config))); + $generatedClasses = $container->get('overblog_graphql.cache_compiler')->compile($this->processConfig($config)); - foreach ($classes as $class) { - $name = $class::getName(); - - $customTypeId = sprintf('overblog_graphql.definition.custom_%s_type', $container->underscore($name)); + foreach ($generatedClasses as $class => $file) { + $aliases = call_user_func($class.'::getAliases'); + $this->setTypeServiceDefinition($container, $class, $aliases); + } + } - $container - ->setDefinition($customTypeId, new Definition($class)) - ->setArguments([new Reference('service_container')]) - ->addTag('overblog_graphql.type', ['alias' => $name]) - ; + private function setTypeServiceDefinition(ContainerBuilder $container, $class, array $aliases) + { + $definition = $container->setDefinition($class, new Definition($class)); + $definition->setPublic(false); + $definition->setAutowired(true); + foreach ($aliases as $alias) { + $definition->addTag('overblog_graphql.type', ['alias' => $alias]); } } diff --git a/DependencyInjection/Compiler/TaggedServiceMappingPass.php b/DependencyInjection/Compiler/TaggedServiceMappingPass.php index 2b197a897..71dc65e64 100644 --- a/DependencyInjection/Compiler/TaggedServiceMappingPass.php +++ b/DependencyInjection/Compiler/TaggedServiceMappingPass.php @@ -11,6 +11,7 @@ namespace Overblog\GraphQLBundle\DependencyInjection\Compiler; +use GraphQL\Type\Definition\Type; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -25,9 +26,25 @@ private function getTaggedServiceMapping(ContainerBuilder $container, $tagName) $taggedServices = $container->findTaggedServiceIds($tagName); foreach ($taggedServices as $id => $tags) { + $className = $container->findDefinition($id)->getClass(); + $isType = is_subclass_of($className, Type::class); foreach ($tags as $tag) { $this->checkRequirements($id, $tag); - $serviceMapping[$tag['alias']] = array_merge($tag, ['id' => $id]); + $tag = array_merge($tag, ['id' => $id]); + if (!$isType) { + $tag['method'] = isset($tag['method']) ? $tag['method'] : '__invoke'; + } + if (isset($tag['alias'])) { + $serviceMapping[$tag['alias']] = $tag; + } + + // add FQCN alias + $alias = $className; + if (!$isType && '__invoke' !== $tag['method']) { + $alias .= '::'.$tag['method']; + } + $tag['alias'] = $alias; + $serviceMapping[$tag['alias']] = $tag; } } @@ -44,18 +61,32 @@ public function process(ContainerBuilder $container) $cleanOptions = $options; $solutionID = $options['id']; - $definition = $container->findDefinition($solutionID); - if (is_subclass_of($definition->getClass(), ContainerAwareInterface::class)) { - $solutionDefinition = $container->findDefinition($options['id']); + $solutionDefinition = $container->findDefinition($options['id']); + + $methods = array_map( + function ($methodCall) { + return $methodCall[0]; + }, + $solutionDefinition->getMethodCalls() + ); + if ( + is_subclass_of($solutionDefinition->getClass(), ContainerAwareInterface::class) + && !in_array('setContainer', $methods) + ) { + @trigger_error( + 'Autowire custom tagged (type, resolver or mutation) services is deprecated as of 0.9 and will be removed in 1.0. Use AutoMapping or set it manually instead.', + E_USER_DEPRECATED + ); $solutionDefinition->addMethodCall('setContainer', [new Reference('service_container')]); } + $resolverDefinition->addMethodCall('addSolution', [$name, new Reference($solutionID), $cleanOptions]); } } protected function checkRequirements($id, array $tag) { - if (empty($tag['alias']) || !is_string($tag['alias'])) { + if (isset($tag['alias']) && !is_string($tag['alias'])) { throw new \InvalidArgumentException( sprintf('Service tagged "%s" must have valid "alias" argument.', $id) ); diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 8d1545665..20d160f9d 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -45,7 +45,7 @@ public function getConfigTreeBuilder() ->addDefaultsIfNotSet() ->children() ->scalarNode('internal_error_message')->defaultNull()->end() - ->booleanNode('show_debug_info')->defaultValue(false)->end() + ->booleanNode('show_debug_info')->defaultFalse()->end() ->booleanNode('config_validation')->defaultValue($this->debug)->end() ->arrayNode('schema') ->beforeNormalization() @@ -70,6 +70,19 @@ public function getConfigTreeBuilder() ->end() ->end() ->end() + ->arrayNode('auto_mapping') + ->treatFalseLike(['enabled' => false]) + ->treatTrueLike(['enabled' => true]) + ->treatNullLike(['enabled' => true]) + ->addDefaultsIfNotSet() + ->children() + ->booleanNode('enabled')->defaultTrue()->end() + ->arrayNode('directories') + ->info('List of directories containing GraphQL classes.') + ->prototype('scalar')->end() + ->end() + ->end() + ->end() ->arrayNode('mappings') ->children() ->arrayNode('types') diff --git a/DependencyInjection/OverblogGraphQLExtension.php b/DependencyInjection/OverblogGraphQLExtension.php index 5901db27f..9a01281ea 100644 --- a/DependencyInjection/OverblogGraphQLExtension.php +++ b/DependencyInjection/OverblogGraphQLExtension.php @@ -11,6 +11,7 @@ namespace Overblog\GraphQLBundle\DependencyInjection; +use GraphQL\Executor\Promise\PromiseAdapter; use GraphQL\Schema; use Overblog\GraphQLBundle\Config\TypeWithOutputFieldsDefinition; use Symfony\Component\Config\FileLocator; @@ -29,7 +30,6 @@ public function load(array $configs, ContainerBuilder $container) $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.yml'); $loader->load('graphql_types.yml'); - $loader->load('graphql_resolvers.yml'); $config = $this->treatConfigs($configs, $container); @@ -43,6 +43,7 @@ public function load(array $configs, ContainerBuilder $container) $this->setConfigBuilders($config); $this->setVersions($config, $container); $this->setShowDebug($config, $container); + $this->setAutoMappingParameters($config, $container); $container->setParameter($this->getAlias().'.resources_dir', realpath(__DIR__.'/../Resources')); } @@ -58,6 +59,12 @@ public function prepend(ContainerBuilder $container) $typesExtension->containerPrependExtensionConfig($config, $container); } + private function setAutoMappingParameters(array $config, ContainerBuilder $container) + { + $container->setParameter($this->getAlias().'.auto_mapping.enabled', $config['definitions']['auto_mapping']['enabled']); + $container->setParameter($this->getAlias().'.auto_mapping.directories', $config['definitions']['auto_mapping']['directories']); + } + private function setExpressionLanguageDefaultParser(ContainerBuilder $container) { $class = version_compare(Kernel::VERSION, '3.2.0', '>=') ? @@ -164,6 +171,10 @@ private function setServicesAliases(array $config, ContainerBuilder $container) foreach ($config['services'] as $name => $id) { $alias = sprintf('%s.%s', $this->getAlias(), $name); $container->setAlias($alias, $id); + // set autowiring types for promise adapter service + if ($this->getAlias().'.promise_adapter' === $alias) { + $container->findDefinition($id)->setAutowiringTypes([PromiseAdapter::class]); + } } } } diff --git a/DependencyInjection/OverblogGraphQLTypesExtension.php b/DependencyInjection/OverblogGraphQLTypesExtension.php index 25465aeae..1c496ebc5 100644 --- a/DependencyInjection/OverblogGraphQLTypesExtension.php +++ b/DependencyInjection/OverblogGraphQLTypesExtension.php @@ -12,33 +12,26 @@ namespace Overblog\GraphQLBundle\DependencyInjection; use Symfony\Component\Config\Resource\FileResource; -use Symfony\Component\Config\Util\XmlUtils; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\SplFileInfo; use Symfony\Component\HttpKernel\DependencyInjection\Extension; -use Symfony\Component\Yaml\Exception\ParseException; -use Symfony\Component\Yaml\Parser as YamlParser; class OverblogGraphQLTypesExtension extends Extension { - private $yamlParser; + private static $configTypes = ['yml', 'xml']; public function load(array $configs, ContainerBuilder $container) { $configuration = $this->getConfiguration($configs, $container); $config = $this->processConfiguration($configuration, $configs); - $container->setParameter('overblog_graphql_types.config', $config); + $container->setParameter($this->getAlias().'.config', $config); } public function containerPrependExtensionConfig(array $config, ContainerBuilder $container) { - $typesMappings = array_merge( - $this->typesConfigsMappingFromConfig($config, $container), - $this->typesConfigsMappingFromBundles($container) - ); + $typesMappings = $this->mappingConfig($config, $container); // treats mappings foreach ($typesMappings as $params) { @@ -55,68 +48,39 @@ private function prependExtensionConfigFromFiles($type, $files, ContainerBuilder { /** @var SplFileInfo $file */ foreach ($files as $file) { - $typeConfig = 'yml' === $type ? $this->typesConfigFromYml($file, $container) : $this->typesConfigFromXml($file, $container); + $parserClass = sprintf('Overblog\\GraphQLBundle\\Config\\Parser\\%sParser', ucfirst($type)); + + $typeConfig = call_user_func($parserClass.'::parse', $file, $container); $container->prependExtensionConfig($this->getAlias(), $typeConfig); } } - private function typesConfigFromXml(SplFileInfo $file, ContainerBuilder $container) + private function mappingConfig(array $config, ContainerBuilder $container) { - $typesConfig = []; - - try { - //@todo fix xml validateSchema - $xml = XmlUtils::loadFile($file->getRealPath()); - foreach ($xml->documentElement->childNodes as $node) { - if (!$node instanceof \DOMElement) { - continue; - } - $values = XmlUtils::convertDomElementToArray($node); - $typesConfig = array_merge($typesConfig, $values); - } - $container->addResource(new FileResource($file->getRealPath())); - } catch (\InvalidArgumentException $e) { - throw new InvalidArgumentException(sprintf('Unable to parse file "%s".', $file), $e->getCode(), $e); - } + $typesMappings = empty($config['definitions']['mappings']['types']) ? [] : $config['definitions']['mappings']['types']; - return $typesConfig; - } - - private function typesConfigFromYml(SplFileInfo $file, ContainerBuilder $container) - { - if (null === $this->yamlParser) { - $this->yamlParser = new YamlParser(); + // app only config files (yml or xml) + if ($container->hasParameter('kernel.root_dir')) { + $typesMappings[] = ['dir' => $container->getParameter('kernel.root_dir').'/config/graphql', 'type' => null]; } - try { - $typesConfig = $this->yamlParser->parse($file->getContents()); - $container->addResource(new FileResource($file->getRealPath())); - } catch (ParseException $e) { - throw new InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML.', $file), 0, $e); - } - - return $typesConfig; - } + $mappingFromBundles = $this->mappingFromBundles($container); + $typesMappings = array_merge($typesMappings, $mappingFromBundles); - private function typesConfigsMappingFromConfig(array $config, ContainerBuilder $container) - { - $typesMappings = []; // from config - if (!empty($config['definitions']['mappings']['types'])) { - $typesMappings = array_filter(array_map( - function (array $typeMapping) use ($container) { - $params = $this->detectConfigFiles($container, $typeMapping['dir'], $typeMapping['type']); - - return $params; - }, - $config['definitions']['mappings']['types'] - )); - } + $typesMappings = array_filter(array_map( + function (array $typeMapping) use ($container) { + $params = $this->detectFilesByType($container, $typeMapping['dir'], $typeMapping['type']); + + return $params; + }, + $typesMappings + )); return $typesMappings; } - private function typesConfigsMappingFromBundles(ContainerBuilder $container) + private function mappingFromBundles(ContainerBuilder $container) { $typesMappings = []; $bundles = $container->getParameter('kernel.bundles'); @@ -126,34 +90,29 @@ private function typesConfigsMappingFromBundles(ContainerBuilder $container) $bundle = new \ReflectionClass($class); $bundleDir = dirname($bundle->getFileName()); - $configPath = $bundleDir.'/'.$this->getMappingResourceConfigDirectory(); - $params = $this->detectConfigFiles($container, $configPath); - - if (null !== $params) { - $typesMappings[] = $params; - } + // only config files (yml or xml) + $typesMappings[] = ['dir' => $bundleDir.'/Resources/config/graphql', 'type' => null]; } return $typesMappings; } - private function detectConfigFiles(ContainerBuilder $container, $configPath, $type = null) + private function detectFilesByType(ContainerBuilder $container, $path, $type = null) { // add the closest existing directory as a resource - $resource = $configPath; + $resource = $path; while (!is_dir($resource)) { $resource = dirname($resource); } $container->addResource(new FileResource($resource)); - $extension = $this->getMappingResourceExtension(); $finder = new Finder(); - $types = null === $type ? ['yml', 'xml'] : [$type]; + $types = null === $type ? self::$configTypes : [$type]; foreach ($types as $type) { try { - $finder->files()->in($configPath)->name('*.'.$extension.'.'.$type); + $finder->files()->in($path)->name('*.types.'.$type); } catch (\InvalidArgumentException $e) { continue; } @@ -168,16 +127,6 @@ private function detectConfigFiles(ContainerBuilder $container, $configPath, $ty return; } - private function getMappingResourceConfigDirectory() - { - return 'Resources/config/graphql'; - } - - private function getMappingResourceExtension() - { - return 'types'; - } - public function getAliasPrefix() { return 'overblog_graphql'; diff --git a/Relay/Mutation/MutationFieldResolver.php b/GraphQL/Relay/Mutation/MutationFieldResolver.php similarity index 65% rename from Relay/Mutation/MutationFieldResolver.php rename to GraphQL/Relay/Mutation/MutationFieldResolver.php index a3cf78e49..6291c31de 100644 --- a/Relay/Mutation/MutationFieldResolver.php +++ b/GraphQL/Relay/Mutation/MutationFieldResolver.php @@ -9,13 +9,15 @@ * file that was distributed with this source code. */ -namespace Overblog\GraphQLBundle\Relay\Mutation; +namespace Overblog\GraphQLBundle\GraphQL\Relay\Mutation; use GraphQL\Executor\Promise\PromiseAdapter; use Overblog\GraphQLBundle\Definition\Argument; +use Overblog\GraphQLBundle\Definition\Resolver\AliasedInterface; +use Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface; use Overblog\GraphQLBundle\Resolver\Resolver; -class MutationFieldResolver +final class MutationFieldResolver implements ResolverInterface, AliasedInterface { /** * @var PromiseAdapter @@ -27,7 +29,7 @@ public function __construct(PromiseAdapter $promiseAdapter) $this->promiseAdapter = $promiseAdapter; } - public function resolve($args, $context, $info, \Closure $mutateAndGetPayloadCallback) + public function __invoke($args, $context, $info, \Closure $mutateAndGetPayloadCallback) { $input = new Argument($args['input']); @@ -38,4 +40,12 @@ public function resolve($args, $context, $info, \Closure $mutateAndGetPayloadCal return $payload; }); } + + /** + * {@inheritdoc} + */ + public static function getAliases() + { + return ['__invoke' => 'relay_mutation_field']; + } } diff --git a/Relay/Node/GlobalIdFieldResolver.php b/GraphQL/Relay/Node/GlobalIdFieldResolver.php similarity index 50% rename from Relay/Node/GlobalIdFieldResolver.php rename to GraphQL/Relay/Node/GlobalIdFieldResolver.php index 8146f13c3..378022738 100644 --- a/Relay/Node/GlobalIdFieldResolver.php +++ b/GraphQL/Relay/Node/GlobalIdFieldResolver.php @@ -9,18 +9,29 @@ * file that was distributed with this source code. */ -namespace Overblog\GraphQLBundle\Relay\Node; +namespace Overblog\GraphQLBundle\GraphQL\Relay\Node; use GraphQL\Type\Definition\ResolveInfo; +use Overblog\GraphQLBundle\Definition\Resolver\AliasedInterface; +use Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface; +use Overblog\GraphQLBundle\Relay\Node\GlobalId; use Overblog\GraphQLBundle\Resolver\Resolver; -class GlobalIdFieldResolver +final class GlobalIdFieldResolver implements ResolverInterface, AliasedInterface { - public function resolve($obj, ResolveInfo $info, $idValue, $typeName) + public function __invoke($obj, ResolveInfo $info, $idValue, $typeName) { return GlobalId::toGlobalId( !empty($typeName) ? $typeName : $info->parentType->name, $idValue ? $idValue : Resolver::valueFromObjectOrArray($obj, 'id') ); } + + /** + * {@inheritdoc} + */ + public static function getAliases() + { + return ['__invoke' => 'relay_globalid_field']; + } } diff --git a/GraphQL/Relay/Node/NodeFieldResolver.php b/GraphQL/Relay/Node/NodeFieldResolver.php new file mode 100644 index 000000000..77ca2bfff --- /dev/null +++ b/GraphQL/Relay/Node/NodeFieldResolver.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\GraphQL\Relay\Node; + +use Overblog\GraphQLBundle\Definition\Resolver\AliasedInterface; +use Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface; + +final class NodeFieldResolver implements ResolverInterface, AliasedInterface +{ + public function __invoke($args, $context, $info, \Closure $idFetcherCallback) + { + return $idFetcherCallback($args['id'], $context, $info); + } + + /** + * {@inheritdoc} + */ + public static function getAliases() + { + return ['__invoke' => 'relay_node_field']; + } +} diff --git a/Relay/Node/PluralIdentifyingRootFieldResolver.php b/GraphQL/Relay/Node/PluralIdentifyingRootFieldResolver.php similarity index 59% rename from Relay/Node/PluralIdentifyingRootFieldResolver.php rename to GraphQL/Relay/Node/PluralIdentifyingRootFieldResolver.php index bd6be71e3..cbf5e96e8 100644 --- a/Relay/Node/PluralIdentifyingRootFieldResolver.php +++ b/GraphQL/Relay/Node/PluralIdentifyingRootFieldResolver.php @@ -9,11 +9,13 @@ * file that was distributed with this source code. */ -namespace Overblog\GraphQLBundle\Relay\Node; +namespace Overblog\GraphQLBundle\GraphQL\Relay\Node; use GraphQL\Executor\Promise\PromiseAdapter; +use Overblog\GraphQLBundle\Definition\Resolver\AliasedInterface; +use Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface; -class PluralIdentifyingRootFieldResolver +final class PluralIdentifyingRootFieldResolver implements ResolverInterface, AliasedInterface { /** * @var PromiseAdapter @@ -25,7 +27,7 @@ public function __construct(PromiseAdapter $promiseAdapter) $this->promiseAdapter = $promiseAdapter; } - public function resolve(array $inputs, $context, $info, callable $resolveSingleInput) + public function __invoke(array $inputs, $context, $info, callable $resolveSingleInput) { $data = []; @@ -35,4 +37,12 @@ public function resolve(array $inputs, $context, $info, callable $resolveSingleI return $this->promiseAdapter->all($data); } + + /** + * {@inheritdoc} + */ + public static function getAliases() + { + return ['__invoke' => 'relay_plural_identifying_field']; + } } diff --git a/OverblogGraphQLBundle.php b/OverblogGraphQLBundle.php index 9ffaecdef..5bacd8124 100644 --- a/OverblogGraphQLBundle.php +++ b/OverblogGraphQLBundle.php @@ -11,12 +11,14 @@ namespace Overblog\GraphQLBundle; +use Overblog\GraphQLBundle\DependencyInjection\Compiler\AutoMappingPass; +use Overblog\GraphQLBundle\DependencyInjection\Compiler\ConfigTypesPass; use Overblog\GraphQLBundle\DependencyInjection\Compiler\MutationTaggedServiceMappingTaggedPass; use Overblog\GraphQLBundle\DependencyInjection\Compiler\ResolverTaggedServiceMappingPass; -use Overblog\GraphQLBundle\DependencyInjection\Compiler\TypesPass; use Overblog\GraphQLBundle\DependencyInjection\Compiler\TypeTaggedServiceMappingPass; use Overblog\GraphQLBundle\DependencyInjection\OverblogGraphQLExtension; use Overblog\GraphQLBundle\DependencyInjection\OverblogGraphQLTypesExtension; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -30,11 +32,14 @@ public function build(ContainerBuilder $container) { parent::build($container); - //TypesPass most be before TypeTaggedServiceMappingPass - $container->addCompilerPass(new TypesPass()); - $container->addCompilerPass(new TypeTaggedServiceMappingPass()); - $container->addCompilerPass(new ResolverTaggedServiceMappingPass()); - $container->addCompilerPass(new MutationTaggedServiceMappingTaggedPass()); + //ConfigTypesPass and AutoMappingPass must be before TypeTaggedServiceMappingPass + $container->addCompilerPass(new AutoMappingPass()); + $container->addCompilerPass(new ConfigTypesPass()); + + $container->addCompilerPass(new TypeTaggedServiceMappingPass(), PassConfig::TYPE_OPTIMIZE); + $container->addCompilerPass(new ResolverTaggedServiceMappingPass(), PassConfig::TYPE_OPTIMIZE); + $container->addCompilerPass(new MutationTaggedServiceMappingTaggedPass(), PassConfig::TYPE_OPTIMIZE); + $container->registerExtension(new OverblogGraphQLTypesExtension()); } diff --git a/Relay/Connection/BackwardConnectionArgsDefinition.php b/Relay/Connection/BackwardConnectionArgsDefinition.php index 2c78cb39b..1f40a0cfb 100644 --- a/Relay/Connection/BackwardConnectionArgsDefinition.php +++ b/Relay/Connection/BackwardConnectionArgsDefinition.php @@ -14,7 +14,7 @@ use GraphQL\Type\Definition\Type; use Overblog\GraphQLBundle\Definition\Builder\MappingInterface; -class BackwardConnectionArgsDefinition implements MappingInterface +final class BackwardConnectionArgsDefinition implements MappingInterface { /** * @param array $config diff --git a/Relay/Connection/ConnectionArgsDefinition.php b/Relay/Connection/ConnectionArgsDefinition.php index 16a092520..a3bf57c6a 100644 --- a/Relay/Connection/ConnectionArgsDefinition.php +++ b/Relay/Connection/ConnectionArgsDefinition.php @@ -14,7 +14,7 @@ use GraphQL\Type\Definition\Type; use Overblog\GraphQLBundle\Definition\Builder\MappingInterface; -class ConnectionArgsDefinition implements MappingInterface +final class ConnectionArgsDefinition implements MappingInterface { /** * @param array $config diff --git a/Relay/Connection/ConnectionDefinition.php b/Relay/Connection/ConnectionDefinition.php index 60ea796f9..f40f8cfa6 100644 --- a/Relay/Connection/ConnectionDefinition.php +++ b/Relay/Connection/ConnectionDefinition.php @@ -13,7 +13,7 @@ use Overblog\GraphQLBundle\Definition\Builder\MappingInterface; -class ConnectionDefinition implements MappingInterface +final class ConnectionDefinition implements MappingInterface { public function toMappingDefinition(array $config) { diff --git a/Relay/Connection/ForwardConnectionArgsDefinition.php b/Relay/Connection/ForwardConnectionArgsDefinition.php index 1105e7d75..3505e4850 100644 --- a/Relay/Connection/ForwardConnectionArgsDefinition.php +++ b/Relay/Connection/ForwardConnectionArgsDefinition.php @@ -14,7 +14,7 @@ use GraphQL\Type\Definition\Type; use Overblog\GraphQLBundle\Definition\Builder\MappingInterface; -class ForwardConnectionArgsDefinition implements MappingInterface +final class ForwardConnectionArgsDefinition implements MappingInterface { /** * @param array $config diff --git a/Relay/Mutation/InputDefinition.php b/Relay/Mutation/InputDefinition.php index 2056a7fec..77bfa6bfa 100644 --- a/Relay/Mutation/InputDefinition.php +++ b/Relay/Mutation/InputDefinition.php @@ -13,7 +13,7 @@ use Overblog\GraphQLBundle\Definition\Builder\MappingInterface; -class InputDefinition implements MappingInterface +final class InputDefinition implements MappingInterface { public function toMappingDefinition(array $config) { diff --git a/Relay/Mutation/MutationFieldDefinition.php b/Relay/Mutation/MutationFieldDefinition.php index 3d7730949..aceeab3bb 100644 --- a/Relay/Mutation/MutationFieldDefinition.php +++ b/Relay/Mutation/MutationFieldDefinition.php @@ -12,8 +12,9 @@ namespace Overblog\GraphQLBundle\Relay\Mutation; use Overblog\GraphQLBundle\Definition\Builder\MappingInterface; +use Overblog\GraphQLBundle\GraphQL\Relay\Mutation\MutationFieldResolver; -class MutationFieldDefinition implements MappingInterface +final class MutationFieldDefinition implements MappingInterface { public function toMappingDefinition(array $config) { @@ -24,13 +25,14 @@ public function toMappingDefinition(array $config) $mutateAndGetPayload = $this->cleanMutateAndGetPayload($config['mutateAndGetPayload']); $payloadType = isset($config['payloadType']) && is_string($config['payloadType']) ? $config['payloadType'] : null; $inputType = isset($config['inputType']) && is_string($config['inputType']) ? $config['inputType'].'!' : null; + $resolver = addslashes(MutationFieldResolver::class); return [ 'type' => $payloadType, 'args' => [ 'input' => ['type' => $inputType], ], - 'resolve' => "@=resolver('relay_mutation_field', [args, context, info, mutateAndGetPayloadCallback($mutateAndGetPayload)])", + 'resolve' => "@=resolver('$resolver', [args, context, info, mutateAndGetPayloadCallback($mutateAndGetPayload)])", ]; } diff --git a/Relay/Mutation/PayloadDefinition.php b/Relay/Mutation/PayloadDefinition.php index 8d88a8e78..3b0eae91f 100644 --- a/Relay/Mutation/PayloadDefinition.php +++ b/Relay/Mutation/PayloadDefinition.php @@ -13,7 +13,7 @@ use Overblog\GraphQLBundle\Definition\Builder\MappingInterface; -class PayloadDefinition implements MappingInterface +final class PayloadDefinition implements MappingInterface { public function toMappingDefinition(array $config) { diff --git a/Relay/Node/GlobalIdFieldDefinition.php b/Relay/Node/GlobalIdFieldDefinition.php index 423716671..a557bc1db 100644 --- a/Relay/Node/GlobalIdFieldDefinition.php +++ b/Relay/Node/GlobalIdFieldDefinition.php @@ -12,18 +12,20 @@ namespace Overblog\GraphQLBundle\Relay\Node; use Overblog\GraphQLBundle\Definition\Builder\MappingInterface; +use Overblog\GraphQLBundle\GraphQL\Relay\Node\GlobalIdFieldResolver; -class GlobalIdFieldDefinition implements MappingInterface +final class GlobalIdFieldDefinition implements MappingInterface { public function toMappingDefinition(array $config) { $typeName = isset($config['typeName']) && is_string($config['typeName']) ? var_export($config['typeName'], true) : 'null'; $idFetcher = isset($config['idFetcher']) && is_string($config['idFetcher']) ? $this->cleanIdFetcher($config['idFetcher']) : 'null'; + $resolver = addslashes(GlobalIdFieldResolver::class); return [ 'description' => 'The ID of an object', 'type' => 'ID!', - 'resolve' => "@=resolver('relay_globalid_field', [value, info, $idFetcher, $typeName])", + 'resolve' => "@=resolver('$resolver', [value, info, $idFetcher, $typeName])", ]; } diff --git a/Relay/Node/NodeDefinition.php b/Relay/Node/NodeDefinition.php index 90deecc32..1ce3cda41 100644 --- a/Relay/Node/NodeDefinition.php +++ b/Relay/Node/NodeDefinition.php @@ -13,7 +13,7 @@ use Overblog\GraphQLBundle\Definition\Builder\MappingInterface; -class NodeDefinition implements MappingInterface +final class NodeDefinition implements MappingInterface { public function toMappingDefinition(array $config) { diff --git a/Relay/Node/NodeFieldDefinition.php b/Relay/Node/NodeFieldDefinition.php index e6eeb80c5..cfb6a109d 100644 --- a/Relay/Node/NodeFieldDefinition.php +++ b/Relay/Node/NodeFieldDefinition.php @@ -12,8 +12,9 @@ namespace Overblog\GraphQLBundle\Relay\Node; use Overblog\GraphQLBundle\Definition\Builder\MappingInterface; +use Overblog\GraphQLBundle\GraphQL\Relay\Node\NodeFieldResolver; -class NodeFieldDefinition implements MappingInterface +final class NodeFieldDefinition implements MappingInterface { public function toMappingDefinition(array $config) { @@ -23,6 +24,7 @@ public function toMappingDefinition(array $config) $idFetcher = $this->cleanIdFetcher($config['idFetcher']); $nodeInterfaceType = isset($config['nodeInterfaceType']) && is_string($config['nodeInterfaceType']) ? $config['nodeInterfaceType'] : null; + $resolver = addslashes(NodeFieldResolver::class); return [ 'description' => 'Fetches an object given its ID', @@ -30,7 +32,7 @@ public function toMappingDefinition(array $config) 'args' => [ 'id' => ['type' => 'ID!', 'description' => 'The ID of an object'], ], - 'resolve' => "@=resolver('relay_node_field', [args, context, info, idFetcherCallback($idFetcher)])", + 'resolve' => "@=resolver('$resolver', [args, context, info, idFetcherCallback($idFetcher)])", ]; } diff --git a/Relay/Node/PluralIdentifyingRootFieldDefinition.php b/Relay/Node/PluralIdentifyingRootFieldDefinition.php index 8581425e2..cbb52a314 100644 --- a/Relay/Node/PluralIdentifyingRootFieldDefinition.php +++ b/Relay/Node/PluralIdentifyingRootFieldDefinition.php @@ -12,8 +12,9 @@ namespace Overblog\GraphQLBundle\Relay\Node; use Overblog\GraphQLBundle\Definition\Builder\MappingInterface; +use Overblog\GraphQLBundle\GraphQL\Relay\Node\PluralIdentifyingRootFieldResolver; -class PluralIdentifyingRootFieldDefinition implements MappingInterface +final class PluralIdentifyingRootFieldDefinition implements MappingInterface { public function toMappingDefinition(array $config) { @@ -34,12 +35,14 @@ public function toMappingDefinition(array $config) } $argName = $config['argName']; + $resolver = addslashes(PluralIdentifyingRootFieldResolver::class); return [ 'type' => "[${config['outputType']}]", 'args' => [$argName => ['type' => "[${config['inputType']}!]!"]], 'resolve' => sprintf( - "@=resolver('relay_plural_identifying_field', [args['$argName'], context, info, resolveSingleInputCallback(%s)])", + "@=resolver('%s', [args['$argName'], context, info, resolveSingleInputCallback(%s)])", + $resolver, $this->cleanResolveSingleInput($config['resolveSingleInput']) ), ]; diff --git a/Resolver/AbstractProxyResolver.php b/Resolver/AbstractProxyResolver.php index ec61525b8..7d4e30279 100644 --- a/Resolver/AbstractProxyResolver.php +++ b/Resolver/AbstractProxyResolver.php @@ -42,7 +42,7 @@ public function resolve($input) } $options = $this->getSolutionOptions($alias); - $func = isset($options['method']) ? [$solution, $options['method']] : $solution; + $func = [$solution, $options['method']]; return call_user_func_array($func, $funcArgs); } diff --git a/Resolver/TypeResolver.php b/Resolver/TypeResolver.php index a31198a8a..4c3316816 100644 --- a/Resolver/TypeResolver.php +++ b/Resolver/TypeResolver.php @@ -12,19 +12,19 @@ namespace Overblog\GraphQLBundle\Resolver; use GraphQL\Type\Definition\Type; -use Overblog\GraphQLBundle\Resolver\Cache\ArrayCache; -use Overblog\GraphQLBundle\Resolver\Cache\CacheInterface; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\Adapter\ArrayAdapter; class TypeResolver extends AbstractResolver { /** - * @var CacheInterface + * @var CacheItemPoolInterface */ - private $cache; + private $cacheAdapter; - public function __construct(CacheInterface $cache = null) + public function __construct(CacheItemPoolInterface $cacheAdapter = null) { - $this->cache = null !== $cache ? $cache : new ArrayCache(); + $this->cacheAdapter = null !== $cacheAdapter ? $cacheAdapter : new ArrayAdapter(); } /** @@ -37,16 +37,14 @@ public function resolve($alias) if (null === $alias) { return; } + $item = $this->cacheAdapter->getItem(md5($alias)); - if (null !== $type = $this->cache->fetch($alias)) { - return $type; + if (!$item->isHit()) { + $item->set($this->string2Type($alias)); + $this->cacheAdapter->save($item); } - $type = $this->string2Type($alias); - - $this->cache->save($alias, $type); - - return $type; + return $item->get(); } private function string2Type($alias) diff --git a/Resources/config/graphql_resolvers.yml b/Resources/config/graphql_resolvers.yml deleted file mode 100644 index b4f3f4415..000000000 --- a/Resources/config/graphql_resolvers.yml +++ /dev/null @@ -1,24 +0,0 @@ -services: - overblog_graphql.resolver.relay_mutation_field: - class: Overblog\GraphQLBundle\Relay\Mutation\MutationFieldResolver - arguments: - - "@overblog_graphql.promise_adapter" - tags: - - { name: overblog_graphql.resolver, alias: "relay_mutation_field", method: "resolve" } - - overblog_graphql.resolver.relay_globalid_field: - class: Overblog\GraphQLBundle\Relay\Node\GlobalIdFieldResolver - tags: - - { name: overblog_graphql.resolver, alias: "relay_globalid_field", method: "resolve" } - - overblog_graphql.resolver.relay_node_field: - class: Overblog\GraphQLBundle\Relay\Node\NodeFieldResolver - tags: - - { name: overblog_graphql.resolver, alias: "relay_node_field", method: "resolve" } - - overblog_graphql.resolver.relay_plural_identifying_field: - class: Overblog\GraphQLBundle\Relay\Node\PluralIdentifyingRootFieldResolver - arguments: - - "@overblog_graphql.promise_adapter" - tags: - - { name: overblog_graphql.resolver, alias: "relay_plural_identifying_field", method: "resolve" } diff --git a/Resources/config/graphql_types.yml b/Resources/config/graphql_types.yml index f9b9c5625..37c944a05 100644 --- a/Resources/config/graphql_types.yml +++ b/Resources/config/graphql_types.yml @@ -2,30 +2,35 @@ services: # GraphQL build-in Scalars types overblog_graphql.definition.string_type: class: GraphQL\Type\Definition\StringType + public: false factory: ["GraphQL\\Type\\Definition\\Type", string] tags: - { name: overblog_graphql.type, alias: String } overblog_graphql.definition.int_type: class: GraphQL\Type\Definition\IntType + public: false factory: ["GraphQL\\Type\\Definition\\Type", int] tags: - { name: overblog_graphql.type, alias: Int } overblog_graphql.definition.float_type: class: GraphQL\Type\Definition\FloatType + public: false factory: ["GraphQL\\Type\\Definition\\Type", Float] tags: - { name: overblog_graphql.type, alias: Float } overblog_graphql.definition.boolean_type: class: GraphQL\Type\Definition\BooleanType + public: false factory: ["GraphQL\\Type\\Definition\\Type", boolean] tags: - { name: overblog_graphql.type, alias: Boolean } overblog_graphql.definition.id_type: class: GraphQL\Type\Definition\IDType + public: false factory: ["GraphQL\\Type\\Definition\\Type", id] tags: - { name: overblog_graphql.type, alias: ID } diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 764a7116e..4a6916e5a 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -76,6 +76,7 @@ services: calls: - ["addUseStatement", ["Symfony\\Component\\DependencyInjection\\ContainerInterface"]] - ["addUseStatement", ["Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface"]] + - ["addImplement", ["Overblog\\GraphQLBundle\\Definition\\Type\\GeneratedTypeInterface"]] - ["setExpressionLanguage", ["@overblog_graphql.expression_language"]] overblog_graphql.event_listener.classloader_listener: diff --git a/Resources/doc/definitions/index.md b/Resources/doc/definitions/index.md index 3ff5afb3b..a80948276 100644 --- a/Resources/doc/definitions/index.md +++ b/Resources/doc/definitions/index.md @@ -7,6 +7,7 @@ Definitions Go further ---------- +* [Resolver](resolver.md) * [Mutation](mutation.md) * [Relay](relay/index.md) * [Builders](builders/index.md) diff --git a/Resources/doc/definitions/mutation.md b/Resources/doc/definitions/mutation.md index 2b65bb8f0..802dc2842 100644 --- a/Resources/doc/definitions/mutation.md +++ b/Resources/doc/definitions/mutation.md @@ -11,7 +11,7 @@ Mutation: type: IntroduceShipPayload! resolve: "@=mutation('create_ship', [args['input']['shipName'], args['input']['factionId']])" args: - #using input object type is optionnal, we use it here to be iso with relay mutation example. + #using input object type is optional, we use it here to be iso with relay mutation example. input: type: IntroduceShipInput! diff --git a/Resources/doc/definitions/resolver.md b/Resources/doc/definitions/resolver.md new file mode 100644 index 000000000..ddc959203 --- /dev/null +++ b/Resources/doc/definitions/resolver.md @@ -0,0 +1,51 @@ +# Resolver + +To ease developments we names 2 types of resolver: + +- `Resolver` that should be use for resolving readonly actions (query) +- `Mutation` that should be use for resolving writing actions (mutation) + +This is just a recommendation. + +Resolvers can be define 2 different ways + +1. **The PHP way** + + You can declare resolver (any class that implements `Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface` + or `Overblog\GraphQLBundle\Definition\Resolver\MutationInterface`) + in `src/*Bundle/GraphQL` or `app/GraphQL` they will be auto discover. + Auto map classes method are accessible by: + * the class method name (example: `AppBunble\GraphQL\CustomResolver::myMethod`) + * the FQCN for callable classes (example: `AppBunble\GraphQL\InvokeResolver` for resolver implementing `__invoke` method) + you can also alias type implementing `Overblog\GraphQLBundle\Definition\Resolver\AliasedInterface` + that returns a map of method/alias. The service created will autowire `__construct` + and `Symfony\Component\DependencyInjection\ContainerAwareInterface::setContainer` methods. + You can also define custom dirs using config: + ```yaml + overblog_graphql: + definitions: + auto_mapping: + directories: + - "%kernel.root_dir%/src/*Bundle/CustomDir" + - "%kernel.root_dir%/src/AppBundle/{foo,bar}" + ``` + To disable auto mapping: + ```yaml + overblog_graphql: + definitions: + auto_mapping: false + ``` + +2. **The service way** + + Creating a service tagged `overblog_graphql.resolver` for resolvers + or `overblog_graphql.mutation` for mutations. + + ```yaml + services: + AppBunble\GraphQL\CustomResolver: + # only for sf < 3.3 + #class: AppBunble\GraphQL\CustomResolver + tags: + - { name: overblog_graphql.resolver, method: add } + ``` diff --git a/Resources/doc/definitions/type-system/index.md b/Resources/doc/definitions/type-system/index.md index 4466f5d1c..81859323e 100644 --- a/Resources/doc/definitions/type-system/index.md +++ b/Resources/doc/definitions/type-system/index.md @@ -4,14 +4,65 @@ Type System Types ----- -Types can be defined in bundle Resources/config/graphql using -this file extension **.types.yml** or **.types.xml**. - -* [Scalars](scalars.md) -* [Object](object.md) -* [Interface](interface.md) -* [Union](union.md) -* [Enum](enum.md) -* [Input Object](input-object.md) -* [Lists](lists.md) -* [Non-Null](non-null.md) +Types can be define 3 different ways: + +1. **The configuration way** + + Creating this file extension **.types.yml** or **.types.xml** + in `src/*Bundle/Resources/config/graphql` or `app/config/graphql`. + See the different possible types: + * [Scalars](scalars.md) + * [Object](object.md) + * [Interface](interface.md) + * [Union](union.md) + * [Enum](enum.md) + * [Input Object](input-object.md) + * [Lists](lists.md) + * [Non-Null](non-null.md) + + You can also define custom dirs using config: + ```yaml + overblog_graphql: + definitions: + mappings: + types: + - + type: yml # or xml + dir: "%kernel.root_dir%/.../mapping" + ``` + +2. **The PHP way** + + You can also declare PHP types (any subclass of `GraphQL\Type\Definition\Type`) + in `src/*Bundle/GraphQL` or `app/GraphQL` + they will be auto discover (thanks to auto mapping). Auto map classes are accessible by FQCN + (example: `AppBunble\GraphQL\Type\DateTimeType`), you can also alias type adding + a public static function `getAliases` + that returns an array of aliases. + You can also define custom dirs using config: + ```yaml + overblog_graphql: + definitions: + auto_mapping: + directories: + - "%kernel.root_dir%/src/*Bundle/CustomDir" + - "%kernel.root_dir%/src/AppBundle/{foo,bar}" + ``` + To disable auto mapping: + ```yaml + overblog_graphql: + definitions: + auto_mapping: false + ``` + +3. **The service way** + + Creating a service tagged `overblog_graphql.type` + ```yaml + services: + AppBundle\GraphQL\Type\DateTime: + # only for sf < 3.3 + #class: AppBundle\GraphQL\Type\DateTime + tags: + - { name: overblog_graphql.type, alias: DateTime } + ``` diff --git a/Resources/skeleton/TypeSystem.php.skeleton b/Resources/skeleton/TypeSystem.php.skeleton index 635d39725..fe2892a91 100644 --- a/Resources/skeleton/TypeSystem.php.skeleton +++ b/Resources/skeleton/TypeSystem.php.skeleton @@ -22,12 +22,6 @@ parent::__construct(); } -public static function getName() -{ -$class = substr(strrchr(__CLASS__, "\\"), 1); -return substr($class, 0, -4); -} - private static function applyPublicFilters($fields) { $filtered = []; @@ -42,4 +36,9 @@ } return $filtered; } + +public static function getAliases() +{ +return [preg_replace('/Type$/', '', substr(strrchr(__CLASS__, '\\'), 1))]; +} } diff --git a/Tests/DependencyInjection/Compiler/ResolverTestService.php b/Tests/DependencyInjection/Compiler/ResolverTestService.php index 0016ed42c..9e6a99fc8 100644 --- a/Tests/DependencyInjection/Compiler/ResolverTestService.php +++ b/Tests/DependencyInjection/Compiler/ResolverTestService.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace DependencyInjection\Compiler; +namespace Overblog\GraphQLBundle\Tests\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerAwareTrait; diff --git a/Tests/Functional/app/Exception/ExampleException.php b/Tests/Functional/App/Exception/ExampleException.php similarity index 86% rename from Tests/Functional/app/Exception/ExampleException.php rename to Tests/Functional/App/Exception/ExampleException.php index 88eabc609..1e2631eef 100644 --- a/Tests/Functional/app/Exception/ExampleException.php +++ b/Tests/Functional/App/Exception/ExampleException.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Overblog\GraphQLBundle\Tests\Functional\app\Exception; +namespace Overblog\GraphQLBundle\Tests\Functional\App\Exception; class ExampleException { diff --git a/Tests/Functional/App/GraphQL/HelloWord/Mutation/CalcMutation.php b/Tests/Functional/App/GraphQL/HelloWord/Mutation/CalcMutation.php new file mode 100644 index 000000000..bee1067d4 --- /dev/null +++ b/Tests/Functional/App/GraphQL/HelloWord/Mutation/CalcMutation.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\Tests\Functional\App\GraphQL\HelloWord\Mutation; + +use Overblog\GraphQLBundle\Definition\Resolver\AliasedInterface; +use Overblog\GraphQLBundle\Definition\Resolver\MutationInterface; + +final class CalcMutation implements MutationInterface, AliasedInterface +{ + public function add($x, $y) + { + return $x + $y; + } + + /** + * {@inheritdoc} + */ + public static function getAliases() + { + return ['add' => 'sum']; + } +} diff --git a/Tests/Functional/App/GraphQL/HelloWord/Type/MutationType.php b/Tests/Functional/App/GraphQL/HelloWord/Type/MutationType.php new file mode 100644 index 000000000..e41269443 --- /dev/null +++ b/Tests/Functional/App/GraphQL/HelloWord/Type/MutationType.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\Tests\Functional\App\GraphQL\HelloWord\Type; + +use GraphQL\Type\Definition\ObjectType; +use GraphQL\Type\Definition\Type; +use Overblog\GraphQLBundle\Resolver\MutationResolver; + +final class MutationType extends ObjectType +{ + public function __construct(MutationResolver $mutator) + { + parent::__construct([ + 'name' => 'Calc', + 'fields' => [ + 'sum' => [ + 'type' => Type::int(), + 'args' => [ + 'x' => ['type' => Type::int()], + 'y' => ['type' => Type::int()], + ], + 'resolve' => function ($root, $args) use ($mutator) { + return $mutator->resolve([ + 'sum', + [$args['x'], $args['y']], + ]); + }, + ], + ], + ]); + } +} diff --git a/Tests/Functional/App/GraphQL/HelloWord/Type/QueryType.php b/Tests/Functional/App/GraphQL/HelloWord/Type/QueryType.php new file mode 100644 index 000000000..d310c3b6c --- /dev/null +++ b/Tests/Functional/App/GraphQL/HelloWord/Type/QueryType.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\Tests\Functional\App\GraphQL\HelloWord\Type; + +use GraphQL\Type\Definition\ObjectType; +use GraphQL\Type\Definition\Type; +use Overblog\GraphQLBundle\Resolver\ResolverResolver; +use Overblog\GraphQLBundle\Tests\Functional\App\IsolatedResolver\EchoResolver; + +final class QueryType extends ObjectType +{ + public function __construct(ResolverResolver $resolver) + { + parent::__construct([ + 'name' => 'Query', + 'fields' => [ + 'echo' => [ + 'type' => Type::string(), + 'args' => [ + 'message' => ['type' => Type::string()], + ], + 'resolve' => function ($root, $args) use ($resolver) { + return $resolver->resolve([ + EchoResolver::class, + [$args['message']], + ]); + }, + ], + ], + ]); + } +} diff --git a/Tests/Functional/App/IsolatedResolver/EchoResolver.php b/Tests/Functional/App/IsolatedResolver/EchoResolver.php new file mode 100644 index 000000000..2c613b886 --- /dev/null +++ b/Tests/Functional/App/IsolatedResolver/EchoResolver.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\Tests\Functional\App\IsolatedResolver; + +use Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface; +use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Symfony\Component\DependencyInjection\ContainerAwareTrait; + +final class EchoResolver implements ResolverInterface, ContainerAwareInterface +{ + use ContainerAwareTrait; + + public function __invoke($message) + { + return $this->container->getParameter('echo.prefix').$message; + } +} diff --git a/Tests/Functional/app/Mutation/SimpleMutationWithThunkFieldsMutation.php b/Tests/Functional/App/Mutation/SimpleMutationWithThunkFieldsMutation.php similarity index 92% rename from Tests/Functional/app/Mutation/SimpleMutationWithThunkFieldsMutation.php rename to Tests/Functional/App/Mutation/SimpleMutationWithThunkFieldsMutation.php index c0da5c60b..e72b75379 100644 --- a/Tests/Functional/app/Mutation/SimpleMutationWithThunkFieldsMutation.php +++ b/Tests/Functional/App/Mutation/SimpleMutationWithThunkFieldsMutation.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Overblog\GraphQLBundle\Tests\Functional\app\Mutation; +namespace Overblog\GraphQLBundle\Tests\Functional\App\Mutation; class SimpleMutationWithThunkFieldsMutation { diff --git a/Tests/Functional/app/Mutation/SimplePromiseMutation.php b/Tests/Functional/App/Mutation/SimplePromiseMutation.php similarity index 90% rename from Tests/Functional/app/Mutation/SimplePromiseMutation.php rename to Tests/Functional/App/Mutation/SimplePromiseMutation.php index 9c9c0d32d..1e67d8d01 100644 --- a/Tests/Functional/app/Mutation/SimplePromiseMutation.php +++ b/Tests/Functional/App/Mutation/SimplePromiseMutation.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Overblog\GraphQLBundle\Tests\Functional\app\Mutation; +namespace Overblog\GraphQLBundle\Tests\Functional\App\Mutation; use GraphQL\Executor\Promise\PromiseAdapter; diff --git a/Tests/Functional/app/Resolver/ConnectionResolver.php b/Tests/Functional/App/Resolver/ConnectionResolver.php similarity index 97% rename from Tests/Functional/app/Resolver/ConnectionResolver.php rename to Tests/Functional/App/Resolver/ConnectionResolver.php index 904dd9a81..02cbc73d0 100644 --- a/Tests/Functional/app/Resolver/ConnectionResolver.php +++ b/Tests/Functional/App/Resolver/ConnectionResolver.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Overblog\GraphQLBundle\Tests\Functional\app\Resolver; +namespace Overblog\GraphQLBundle\Tests\Functional\App\Resolver; use GraphQL\Deferred; use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter; diff --git a/Tests/Functional/app/Resolver/GlobalResolver.php b/Tests/Functional/App/Resolver/GlobalResolver.php similarity index 97% rename from Tests/Functional/app/Resolver/GlobalResolver.php rename to Tests/Functional/App/Resolver/GlobalResolver.php index 7b6a17077..81c12f37d 100644 --- a/Tests/Functional/app/Resolver/GlobalResolver.php +++ b/Tests/Functional/App/Resolver/GlobalResolver.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Overblog\GraphQLBundle\Tests\Functional\app\Resolver; +namespace Overblog\GraphQLBundle\Tests\Functional\App\Resolver; use Overblog\GraphQLBundle\Relay\Node\GlobalId; use Overblog\GraphQLBundle\Resolver\TypeResolver; diff --git a/Tests/Functional/app/Resolver/NodeResolver.php b/Tests/Functional/App/Resolver/NodeResolver.php similarity index 86% rename from Tests/Functional/app/Resolver/NodeResolver.php rename to Tests/Functional/App/Resolver/NodeResolver.php index 958c9af3a..624e0bd8c 100644 --- a/Tests/Functional/app/Resolver/NodeResolver.php +++ b/Tests/Functional/App/Resolver/NodeResolver.php @@ -9,13 +9,17 @@ * file that was distributed with this source code. */ -namespace Overblog\GraphQLBundle\Tests\Functional\app\Resolver; +namespace Overblog\GraphQLBundle\Tests\Functional\App\Resolver; use GraphQL\Type\Definition\ResolveInfo; use Overblog\GraphQLBundle\Resolver\TypeResolver; +use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Symfony\Component\DependencyInjection\ContainerAwareTrait; -class NodeResolver +class NodeResolver implements ContainerAwareInterface { + use ContainerAwareTrait; + /** @var TypeResolver */ private $typeResolver; diff --git a/Tests/Functional/app/Resolver/PluralResolver.php b/Tests/Functional/App/Resolver/PluralResolver.php similarity index 90% rename from Tests/Functional/app/Resolver/PluralResolver.php rename to Tests/Functional/App/Resolver/PluralResolver.php index 4c0c43c4f..83d879e98 100644 --- a/Tests/Functional/app/Resolver/PluralResolver.php +++ b/Tests/Functional/App/Resolver/PluralResolver.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Overblog\GraphQLBundle\Tests\Functional\app\Resolver; +namespace Overblog\GraphQLBundle\Tests\Functional\App\Resolver; use GraphQL\Type\Definition\ResolveInfo; diff --git a/Tests/Functional/app/AppKernel.php b/Tests/Functional/App/TestKernel.php similarity index 76% rename from Tests/Functional/app/AppKernel.php rename to Tests/Functional/App/TestKernel.php index 80f843b73..2d0b6651f 100644 --- a/Tests/Functional/app/AppKernel.php +++ b/Tests/Functional/App/TestKernel.php @@ -9,15 +9,16 @@ * file that was distributed with this source code. */ -namespace Overblog\GraphQLBundle\Tests\Functional\app; +namespace Overblog\GraphQLBundle\Tests\Functional\App; +use Overblog\GraphQLBundle\OverblogGraphQLBundle; +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; +use Symfony\Bundle\SecurityBundle\SecurityBundle; +use Symfony\Bundle\TwigBundle\TwigBundle; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\HttpKernel\Kernel; -/** - * AppKernel. - */ -class AppKernel extends Kernel +final class TestKernel extends Kernel { private $testCase; @@ -27,10 +28,10 @@ class AppKernel extends Kernel public function registerBundles() { return [ - new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), - new \Symfony\Bundle\SecurityBundle\SecurityBundle(), - new \Symfony\Bundle\TwigBundle\TwigBundle(), - new \Overblog\GraphQLBundle\OverblogGraphQLBundle(), + new FrameworkBundle(), + new SecurityBundle(), + new TwigBundle(), + new OverblogGraphQLBundle(), ]; } diff --git a/Tests/Functional/app/config/access/config.yml b/Tests/Functional/App/config/access/config.yml similarity index 100% rename from Tests/Functional/app/config/access/config.yml rename to Tests/Functional/App/config/access/config.yml diff --git a/Tests/Functional/app/config/access/mapping/access.types.yml b/Tests/Functional/App/config/access/mapping/access.types.yml similarity index 100% rename from Tests/Functional/app/config/access/mapping/access.types.yml rename to Tests/Functional/App/config/access/mapping/access.types.yml diff --git a/Tests/Functional/App/config/autoMapping/config.yml b/Tests/Functional/App/config/autoMapping/config.yml new file mode 100644 index 000000000..776add1d1 --- /dev/null +++ b/Tests/Functional/App/config/autoMapping/config.yml @@ -0,0 +1,13 @@ +imports: + - { resource: ../config.yml } +parameters: + echo.prefix: "You said: " + +overblog_graphql: + definitions: + auto_mapping: + enabled: true + directories: ["%kernel.root_dir%/IsolatedResolver"] + schema: + query: "Overblog\\GraphQLBundle\\Tests\\Functional\\App\\GraphQL\\HelloWord\\Type\\QueryType" + mutation: "Overblog\\GraphQLBundle\\Tests\\Functional\\App\\GraphQL\\HelloWord\\Type\\MutationType" diff --git a/Tests/Functional/app/config/config.yml b/Tests/Functional/App/config/config.yml similarity index 92% rename from Tests/Functional/app/config/config.yml rename to Tests/Functional/App/config/config.yml index 901012314..38498707b 100644 --- a/Tests/Functional/app/config/config.yml +++ b/Tests/Functional/App/config/config.yml @@ -11,6 +11,7 @@ framework: overblog_graphql: definitions: config_validation: true + auto_mapping: false services: #disable twig error pages diff --git a/Tests/Functional/app/config/connection/config.yml b/Tests/Functional/App/config/connection/config.yml similarity index 100% rename from Tests/Functional/app/config/connection/config.yml rename to Tests/Functional/App/config/connection/config.yml diff --git a/Tests/Functional/app/config/connection/mapping/connection.types.yml b/Tests/Functional/App/config/connection/mapping/connection.types.yml similarity index 100% rename from Tests/Functional/app/config/connection/mapping/connection.types.yml rename to Tests/Functional/App/config/connection/mapping/connection.types.yml diff --git a/Tests/Functional/app/config/connection/services.yml b/Tests/Functional/App/config/connection/services.yml similarity index 89% rename from Tests/Functional/app/config/connection/services.yml rename to Tests/Functional/App/config/connection/services.yml index d2777a4ba..2e8809ac0 100644 --- a/Tests/Functional/app/config/connection/services.yml +++ b/Tests/Functional/App/config/connection/services.yml @@ -1,6 +1,6 @@ services: overblog_graphql.test.resolver.node: - class: Overblog\GraphQLBundle\Tests\Functional\app\Resolver\ConnectionResolver + class: Overblog\GraphQLBundle\Tests\Functional\App\Resolver\ConnectionResolver arguments: - "@overblog_graphql.promise_adapter" tags: diff --git a/Tests/Functional/app/config/customScalar/config.yml b/Tests/Functional/App/config/customScalar/config.yml similarity index 100% rename from Tests/Functional/app/config/customScalar/config.yml rename to Tests/Functional/App/config/customScalar/config.yml diff --git a/Tests/Functional/app/config/customScalar/mapping/Query.types.yml b/Tests/Functional/App/config/customScalar/mapping/Query.types.yml similarity index 100% rename from Tests/Functional/app/config/customScalar/mapping/Query.types.yml rename to Tests/Functional/App/config/customScalar/mapping/Query.types.yml diff --git a/Tests/Functional/app/config/definition/config.yml b/Tests/Functional/App/config/definition/config.yml similarity index 100% rename from Tests/Functional/app/config/definition/config.yml rename to Tests/Functional/App/config/definition/config.yml diff --git a/Tests/Functional/app/config/definition/mapping/deprecated.types.yml b/Tests/Functional/App/config/definition/mapping/deprecated.types.yml similarity index 100% rename from Tests/Functional/app/config/definition/mapping/deprecated.types.yml rename to Tests/Functional/App/config/definition/mapping/deprecated.types.yml diff --git a/Tests/Functional/app/config/exception/config.yml b/Tests/Functional/App/config/exception/config.yml similarity index 100% rename from Tests/Functional/app/config/exception/config.yml rename to Tests/Functional/App/config/exception/config.yml diff --git a/Tests/Functional/app/config/exception/mapping/exception.types.yml b/Tests/Functional/App/config/exception/mapping/exception.types.yml similarity index 100% rename from Tests/Functional/app/config/exception/mapping/exception.types.yml rename to Tests/Functional/App/config/exception/mapping/exception.types.yml diff --git a/Tests/Functional/app/config/exception/services.yml b/Tests/Functional/App/config/exception/services.yml similarity index 71% rename from Tests/Functional/app/config/exception/services.yml rename to Tests/Functional/App/config/exception/services.yml index ea0cd02cf..506993fff 100644 --- a/Tests/Functional/app/config/exception/services.yml +++ b/Tests/Functional/App/config/exception/services.yml @@ -1,5 +1,5 @@ services: overblog_graphql.test.exception: - class: Overblog\GraphQLBundle\Tests\Functional\app\Exception\ExampleException + class: Overblog\GraphQLBundle\Tests\Functional\App\Exception\ExampleException tags: - { name: "overblog_graphql.resolver", alias: "example_exception" } diff --git a/Tests/Functional/app/config/global/config.yml b/Tests/Functional/App/config/global/config.yml similarity index 89% rename from Tests/Functional/app/config/global/config.yml rename to Tests/Functional/App/config/global/config.yml index 568f0e684..d7538956f 100644 --- a/Tests/Functional/app/config/global/config.yml +++ b/Tests/Functional/App/config/global/config.yml @@ -6,7 +6,7 @@ parameters: services: overblog_graphql.test.resolver.global: - class: Overblog\GraphQLBundle\Tests\Functional\app\Resolver\GlobalResolver + class: Overblog\GraphQLBundle\Tests\Functional\App\Resolver\GlobalResolver arguments: - "@overblog_graphql.type_resolver" diff --git a/Tests/Functional/app/config/global/mapping/global.types.yml b/Tests/Functional/App/config/global/mapping/global.types.yml similarity index 100% rename from Tests/Functional/app/config/global/mapping/global.types.yml rename to Tests/Functional/App/config/global/mapping/global.types.yml diff --git a/Tests/Functional/app/config/mutation/config.yml b/Tests/Functional/App/config/mutation/config.yml similarity index 100% rename from Tests/Functional/app/config/mutation/config.yml rename to Tests/Functional/App/config/mutation/config.yml diff --git a/Tests/Functional/app/config/mutation/mapping/Inputs.types.yml b/Tests/Functional/App/config/mutation/mapping/Inputs.types.yml similarity index 100% rename from Tests/Functional/app/config/mutation/mapping/Inputs.types.yml rename to Tests/Functional/App/config/mutation/mapping/Inputs.types.yml diff --git a/Tests/Functional/app/config/mutation/mapping/Payloads.types.yml b/Tests/Functional/App/config/mutation/mapping/Payloads.types.yml similarity index 100% rename from Tests/Functional/app/config/mutation/mapping/Payloads.types.yml rename to Tests/Functional/App/config/mutation/mapping/Payloads.types.yml diff --git a/Tests/Functional/app/config/mutation/mapping/RootMutation.types.yml b/Tests/Functional/App/config/mutation/mapping/RootMutation.types.yml similarity index 100% rename from Tests/Functional/app/config/mutation/mapping/RootMutation.types.yml rename to Tests/Functional/App/config/mutation/mapping/RootMutation.types.yml diff --git a/Tests/Functional/app/config/mutation/services.yml b/Tests/Functional/App/config/mutation/services.yml similarity index 80% rename from Tests/Functional/app/config/mutation/services.yml rename to Tests/Functional/App/config/mutation/services.yml index e22701429..6e7dbad7b 100644 --- a/Tests/Functional/app/config/mutation/services.yml +++ b/Tests/Functional/App/config/mutation/services.yml @@ -1,11 +1,11 @@ services: overblog_graphql.test.simple_mutation_with_thunk_fields: - class: Overblog\GraphQLBundle\Tests\Functional\app\Mutation\SimpleMutationWithThunkFieldsMutation + class: Overblog\GraphQLBundle\Tests\Functional\App\Mutation\SimpleMutationWithThunkFieldsMutation tags: - { name: "overblog_graphql.mutation", alias: "simple_mutation_with_thunk_fields", method: "mutate" } overblog_graphql.test.simple_promise_mutation: - class: Overblog\GraphQLBundle\Tests\Functional\app\Mutation\SimplePromiseMutation + class: Overblog\GraphQLBundle\Tests\Functional\App\Mutation\SimplePromiseMutation arguments: - "@overblog_graphql.react.promise_adapter" tags: diff --git a/Tests/Functional/app/config/node/config.yml b/Tests/Functional/App/config/node/config.yml similarity index 93% rename from Tests/Functional/app/config/node/config.yml rename to Tests/Functional/App/config/node/config.yml index 6347af0a8..8bf77d792 100644 --- a/Tests/Functional/app/config/node/config.yml +++ b/Tests/Functional/App/config/node/config.yml @@ -6,7 +6,7 @@ parameters: services: overblog_graphql.test.resolver.node: - class: Overblog\GraphQLBundle\Tests\Functional\app\Resolver\NodeResolver + class: Overblog\GraphQLBundle\Tests\Functional\App\Resolver\NodeResolver arguments: - "@overblog_graphql.type_resolver" tags: diff --git a/Tests/Functional/app/config/node/mapping/Node.types.yml b/Tests/Functional/App/config/node/mapping/Node.types.yml similarity index 100% rename from Tests/Functional/app/config/node/mapping/Node.types.yml rename to Tests/Functional/App/config/node/mapping/Node.types.yml diff --git a/Tests/Functional/app/config/node/mapping/Photo.types.yml b/Tests/Functional/App/config/node/mapping/Photo.types.yml similarity index 100% rename from Tests/Functional/app/config/node/mapping/Photo.types.yml rename to Tests/Functional/App/config/node/mapping/Photo.types.yml diff --git a/Tests/Functional/app/config/node/mapping/Query.types.yml b/Tests/Functional/App/config/node/mapping/Query.types.yml similarity index 100% rename from Tests/Functional/app/config/node/mapping/Query.types.yml rename to Tests/Functional/App/config/node/mapping/Query.types.yml diff --git a/Tests/Functional/app/config/node/mapping/User.types.yml b/Tests/Functional/App/config/node/mapping/User.types.yml similarity index 100% rename from Tests/Functional/app/config/node/mapping/User.types.yml rename to Tests/Functional/App/config/node/mapping/User.types.yml diff --git a/Tests/Functional/app/config/plural/config.yml b/Tests/Functional/App/config/plural/config.yml similarity index 90% rename from Tests/Functional/app/config/plural/config.yml rename to Tests/Functional/App/config/plural/config.yml index 8963a4f70..4ca04b16e 100644 --- a/Tests/Functional/app/config/plural/config.yml +++ b/Tests/Functional/App/config/plural/config.yml @@ -6,7 +6,7 @@ parameters: services: overblog_graphql.test.resolver.plural: - class: Overblog\GraphQLBundle\Tests\Functional\app\Resolver\PluralResolver + class: Overblog\GraphQLBundle\Tests\Functional\App\Resolver\PluralResolver tags: - { name: "overblog_graphql.resolver", alias: "plural_single_input", method: "resolveSingleInput" } diff --git a/Tests/Functional/app/config/plural/mapping/Query.types.xml b/Tests/Functional/App/config/plural/mapping/Query.types.xml similarity index 100% rename from Tests/Functional/app/config/plural/mapping/Query.types.xml rename to Tests/Functional/App/config/plural/mapping/Query.types.xml diff --git a/Tests/Functional/app/config/plural/mapping/User.types.xml b/Tests/Functional/App/config/plural/mapping/User.types.xml similarity index 100% rename from Tests/Functional/app/config/plural/mapping/User.types.xml rename to Tests/Functional/App/config/plural/mapping/User.types.xml diff --git a/Tests/Functional/app/config/public/config.yml b/Tests/Functional/App/config/public/config.yml similarity index 100% rename from Tests/Functional/app/config/public/config.yml rename to Tests/Functional/App/config/public/config.yml diff --git a/Tests/Functional/app/config/public/mapping/public.types.yml b/Tests/Functional/App/config/public/mapping/public.types.yml similarity index 100% rename from Tests/Functional/app/config/public/mapping/public.types.yml rename to Tests/Functional/App/config/public/mapping/public.types.yml diff --git a/Tests/Functional/app/config/queryComplexity/config.yml b/Tests/Functional/App/config/queryComplexity/config.yml similarity index 100% rename from Tests/Functional/app/config/queryComplexity/config.yml rename to Tests/Functional/App/config/queryComplexity/config.yml diff --git a/Tests/Functional/app/config/queryComplexity/mapping/connectionWithComplexity.types.yml b/Tests/Functional/App/config/queryComplexity/mapping/connectionWithComplexity.types.yml similarity index 100% rename from Tests/Functional/app/config/queryComplexity/mapping/connectionWithComplexity.types.yml rename to Tests/Functional/App/config/queryComplexity/mapping/connectionWithComplexity.types.yml diff --git a/Tests/Functional/app/config/routing.yml b/Tests/Functional/App/config/routing.yml similarity index 100% rename from Tests/Functional/app/config/routing.yml rename to Tests/Functional/App/config/routing.yml diff --git a/Tests/Functional/app/config/security.yml b/Tests/Functional/App/config/security.yml similarity index 100% rename from Tests/Functional/app/config/security.yml rename to Tests/Functional/App/config/security.yml diff --git a/Tests/Functional/AutoMapping/HelloWordTest.php b/Tests/Functional/AutoMapping/HelloWordTest.php new file mode 100644 index 000000000..b9e46fbed --- /dev/null +++ b/Tests/Functional/AutoMapping/HelloWordTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\Tests\Functional\AutoMapping; + +use Overblog\GraphQLBundle\Tests\Functional\TestCase; + +class HelloWordTest extends TestCase +{ + protected function setUp() + { + static::bootKernel(['test_case' => 'autoMapping']); + } + + public function testQuery() + { + $query = 'query { echo(message: "This is my message!") }'; + $expectedData = ['echo' => 'You said: This is my message!']; + + $this->assertGraphQL($query, $expectedData); + } + + public function testMutation() + { + $query = 'mutation { sum(x: 5, y: 15) }'; + $expectedData = ['sum' => '20']; + + $this->assertGraphQL($query, $expectedData); + } +} diff --git a/Tests/Functional/BootTest.php b/Tests/Functional/BootTest.php index 2cb266f66..8aa608b65 100644 --- a/Tests/Functional/BootTest.php +++ b/Tests/Functional/BootTest.php @@ -13,7 +13,7 @@ class BootTest extends TestCase { - public function testBoot() + public function testBootAppKernel() { $kernel = $this->createKernel(); $kernel->boot(); diff --git a/Tests/Functional/Command/DebugCommandTest.php b/Tests/Functional/Command/DebugCommandTest.php new file mode 100644 index 000000000..de0a6aa52 --- /dev/null +++ b/Tests/Functional/Command/DebugCommandTest.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\Tests\Functional\Command; + +use Overblog\GraphQLBundle\Command\DebugCommand; +use Overblog\GraphQLBundle\Tests\Functional\TestCase; +use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Tester\CommandTester; + +class DebugCommandTest extends TestCase +{ + /** + * @var Command + */ + private $command; + + /** + * @var CommandTester + */ + private $commandTester; + + private $logs = []; + + public function setUp() + { + parent::setUp(); + $client = static::createClient(['test_case' => 'mutation']); + $kernel = $client->getKernel(); + + $application = new Application($kernel); + $application->add(new DebugCommand()); + $this->command = $application->find('graphql:debug'); + $this->commandTester = new CommandTester($this->command); + foreach (DebugCommand::getCategories() as $category) { + $this->logs[$category] = trim(str_replace('ΓΈ', '', file_get_contents(__DIR__.'/fixtures/debug-'.$category.'.txt'))); + } + } + + /** + * @param array $categories + * @dataProvider categoryDataProvider + */ + public function testProcess(array $categories) + { + $input = [ + 'command' => $this->command->getName(), + ]; + if (empty($categories)) { + $categories = DebugCommand::getCategories(); + } else { + $input['--category'] = $categories; + } + + $this->commandTester->execute($input); + $this->assertEquals(0, $this->commandTester->getStatusCode()); + $expected = "\n"; + foreach ($categories as $category) { + $expected .= $this->logs[$category]." \n\n\n\n"; + } + + $this->assertEquals($expected, $this->commandTester->getDisplay()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Invalid category (fake) + */ + public function testInvalidFormat() + { + $this->commandTester->execute([ + 'command' => $this->command->getName(), + '--category' => 'fake', + ]); + } + + public function categoryDataProvider() + { + return [ + [[]], + [['type']], + [['resolver']], + [['mutation']], + [['type', 'mutation']], + ]; + } +} diff --git a/Tests/Functional/Command/GraphDumpSchemaCommandTest.php b/Tests/Functional/Command/GraphDumpSchemaCommandTest.php index 3df6ff154..a6814105d 100644 --- a/Tests/Functional/Command/GraphDumpSchemaCommandTest.php +++ b/Tests/Functional/Command/GraphDumpSchemaCommandTest.php @@ -42,7 +42,7 @@ public function setUp() $application = new Application($kernel); $application->add(new GraphQLDumpSchemaCommand()); - $this->command = $application->find('graph:dump-schema'); + $this->command = $application->find('graphql:dump-schema'); $this->commandTester = new CommandTester($this->command); $this->cacheDir = $kernel->getCacheDir(); } @@ -67,7 +67,7 @@ public function testDump($format, $withFormatOption = true) $this->commandTester->execute($input); $this->assertEquals(0, $this->commandTester->getStatusCode()); - $this->assertEquals(trim(file_get_contents(__DIR__.'/schema.'.$format)), trim(file_get_contents($file))); + $this->assertEquals(trim(file_get_contents(__DIR__.'/fixtures/schema.'.$format)), trim(file_get_contents($file))); } /** diff --git a/Tests/Functional/Command/fixtures/debug-mutation.txt b/Tests/Functional/Command/fixtures/debug-mutation.txt new file mode 100644 index 000000000..8742dc9f6 --- /dev/null +++ b/Tests/Functional/Command/fixtures/debug-mutation.txt @@ -0,0 +1,15 @@ + +GraphQL Mutations Services +========================== + + --------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------- + id aliases + --------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------- + overblog_graphql.test.simple_mutation_with_thunk_fields simple_mutation_with_thunk_fields (method: mutate) + Overblog\GraphQLBundle\Tests\Functional\App\Mutation\SimpleMutationWithThunkFieldsMutation::mutate (method: mutate) + overblog_graphql.test.simple_promise_mutation simple_promise_mutation (method: mutate) + Overblog\GraphQLBundle\Tests\Functional\App\Mutation\SimplePromiseMutation::mutate (method: mutate) + --------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------- + + + diff --git a/Tests/Functional/Command/fixtures/debug-resolver.txt b/Tests/Functional/Command/fixtures/debug-resolver.txt new file mode 100644 index 000000000..97e036e1e --- /dev/null +++ b/Tests/Functional/Command/fixtures/debug-resolver.txt @@ -0,0 +1,19 @@ + +GraphQL Resolvers Services +========================== + + ------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------- + id aliases + ------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------- + overblog\graphqlbundle\graphql\relay\mutation\mutationfieldresolver Overblog\GraphQLBundle\GraphQL\Relay\Mutation\MutationFieldResolver (method: __invoke) + relay_mutation_field (method: __invoke) + overblog\graphqlbundle\graphql\relay\node\globalidfieldresolver Overblog\GraphQLBundle\GraphQL\Relay\Node\GlobalIdFieldResolver (method: __invoke) + relay_globalid_field (method: __invoke) + overblog\graphqlbundle\graphql\relay\node\nodefieldresolver Overblog\GraphQLBundle\GraphQL\Relay\Node\NodeFieldResolver (method: __invoke) + relay_node_field (method: __invoke) + overblog\graphqlbundle\graphql\relay\node\pluralidentifyingrootfieldresolver Overblog\GraphQLBundle\GraphQL\Relay\Node\PluralIdentifyingRootFieldResolver (method: __invoke) + relay_plural_identifying_field (method: __invoke) + ------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------- + + + diff --git a/Tests/Functional/Command/fixtures/debug-type.txt b/Tests/Functional/Command/fixtures/debug-type.txt new file mode 100644 index 000000000..509ee368d --- /dev/null +++ b/Tests/Functional/Command/fixtures/debug-type.txt @@ -0,0 +1,37 @@ + +GraphQL Types Services +====================== + + ------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------ + id aliases + ------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------ + overblog\graphqlbundle\mutation\__definitions__\pageinfotype PageInfo + Overblog\GraphQLBundle\Mutation\__DEFINITIONS__\PageInfoType + overblog\graphqlbundle\mutation\__definitions__\rootmutationtype RootMutation + Overblog\GraphQLBundle\Mutation\__DEFINITIONS__\RootMutationType + overblog\graphqlbundle\mutation\__definitions__\simplemutationinputtype simpleMutationInput + Overblog\GraphQLBundle\Mutation\__DEFINITIONS__\simpleMutationInputType + overblog\graphqlbundle\mutation\__definitions__\simplemutationpayloadtype simpleMutationPayload + Overblog\GraphQLBundle\Mutation\__DEFINITIONS__\simpleMutationPayloadType + overblog\graphqlbundle\mutation\__definitions__\simplemutationwiththunkfieldsinputtype simpleMutationWithThunkFieldsInput + Overblog\GraphQLBundle\Mutation\__DEFINITIONS__\simpleMutationWithThunkFieldsInputType + overblog\graphqlbundle\mutation\__definitions__\simplemutationwiththunkfieldspayloadtype simpleMutationWithThunkFieldsPayload + Overblog\GraphQLBundle\Mutation\__DEFINITIONS__\simpleMutationWithThunkFieldsPayloadType + overblog\graphqlbundle\mutation\__definitions__\simplepromisemutationinputtype simplePromiseMutationInput + Overblog\GraphQLBundle\Mutation\__DEFINITIONS__\simplePromiseMutationInputType + overblog\graphqlbundle\mutation\__definitions__\simplepromisemutationpayloadtype simplePromiseMutationPayload + Overblog\GraphQLBundle\Mutation\__DEFINITIONS__\simplePromiseMutationPayloadType + overblog_graphql.definition.boolean_type Boolean + GraphQL\Type\Definition\BooleanType + overblog_graphql.definition.float_type Float + GraphQL\Type\Definition\FloatType + overblog_graphql.definition.id_type ID + GraphQL\Type\Definition\IDType + overblog_graphql.definition.int_type Int + GraphQL\Type\Definition\IntType + overblog_graphql.definition.string_type String + GraphQL\Type\Definition\StringType + ------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------ + + + diff --git a/Tests/Functional/Command/schema.graphqls b/Tests/Functional/Command/fixtures/schema.graphqls similarity index 100% rename from Tests/Functional/Command/schema.graphqls rename to Tests/Functional/Command/fixtures/schema.graphqls diff --git a/Tests/Functional/Command/schema.json b/Tests/Functional/Command/fixtures/schema.json similarity index 100% rename from Tests/Functional/Command/schema.json rename to Tests/Functional/Command/fixtures/schema.json diff --git a/Tests/Functional/Exception/ExceptionTest.php b/Tests/Functional/Exception/ExceptionTest.php index b4eb17639..bd841bb4c 100644 --- a/Tests/Functional/Exception/ExceptionTest.php +++ b/Tests/Functional/Exception/ExceptionTest.php @@ -24,7 +24,7 @@ protected function setUp() { parent::setUp(); - static::createAndBootKernel(['test_case' => 'exception']); + static::bootKernel(['test_case' => 'exception']); } public function testExceptionIsMappedToAWarning() diff --git a/Tests/Functional/Relay/Connection/ConnectionTest.php b/Tests/Functional/Relay/Connection/ConnectionTest.php index f8054537e..6fe0b5b99 100644 --- a/Tests/Functional/Relay/Connection/ConnectionTest.php +++ b/Tests/Functional/Relay/Connection/ConnectionTest.php @@ -24,7 +24,7 @@ protected function setUp() { parent::setUp(); - static::createAndBootKernel(['test_case' => 'connection']); + static::bootKernel(['test_case' => 'connection']); } public function testIncludesConnectionAndEdgeFields() diff --git a/Tests/Functional/Relay/Mutation/MutationTest.php b/Tests/Functional/Relay/Mutation/MutationTest.php index 28a01b419..7c9b39760 100644 --- a/Tests/Functional/Relay/Mutation/MutationTest.php +++ b/Tests/Functional/Relay/Mutation/MutationTest.php @@ -24,7 +24,7 @@ protected function setUp() { parent::setUp(); - static::createAndBootKernel(['test_case' => 'mutation']); + static::bootKernel(['test_case' => 'mutation']); } public function testRequiresAnArgument() diff --git a/Tests/Functional/Relay/Node/GlobalTest.php b/Tests/Functional/Relay/Node/GlobalTest.php index e2a2538b1..a63c982da 100644 --- a/Tests/Functional/Relay/Node/GlobalTest.php +++ b/Tests/Functional/Relay/Node/GlobalTest.php @@ -24,7 +24,7 @@ protected function setUp() { parent::setUp(); - static::createAndBootKernel(['test_case' => 'global']); + static::bootKernel(['test_case' => 'global']); } public function testGlobalIdFields() diff --git a/Tests/Functional/Relay/Node/NodeTest.php b/Tests/Functional/Relay/Node/NodeTest.php index d8c0ea787..118affec0 100644 --- a/Tests/Functional/Relay/Node/NodeTest.php +++ b/Tests/Functional/Relay/Node/NodeTest.php @@ -24,7 +24,7 @@ protected function setUp() { parent::setUp(); - static::createAndBootKernel(['test_case' => 'node']); + static::bootKernel(['test_case' => 'node']); } public function testNodeInterfaceAndFields() diff --git a/Tests/Functional/Relay/Node/PluralTest.php b/Tests/Functional/Relay/Node/PluralTest.php index 68ee80765..8f86d217e 100644 --- a/Tests/Functional/Relay/Node/PluralTest.php +++ b/Tests/Functional/Relay/Node/PluralTest.php @@ -24,7 +24,7 @@ protected function setUp() { parent::setUp(); - static::createAndBootKernel(['test_case' => 'plural']); + static::bootKernel(['test_case' => 'plural']); } public function testNodeInterfaceAndFields() diff --git a/Tests/Functional/Security/AccessTest.php b/Tests/Functional/Security/AccessTest.php index 23889b538..1f5716947 100644 --- a/Tests/Functional/Security/AccessTest.php +++ b/Tests/Functional/Security/AccessTest.php @@ -11,7 +11,7 @@ namespace Overblog\GraphQLBundle\Tests\Functional\Security; -use Overblog\GraphQLBundle\Tests\Functional\app\Mutation\SimpleMutationWithThunkFieldsMutation; +use Overblog\GraphQLBundle\Tests\Functional\App\Mutation\SimpleMutationWithThunkFieldsMutation; use Overblog\GraphQLBundle\Tests\Functional\TestCase; class AccessTest extends TestCase diff --git a/Tests/Functional/TestCase.php b/Tests/Functional/TestCase.php index 84461febf..c4ea7623f 100644 --- a/Tests/Functional/TestCase.php +++ b/Tests/Functional/TestCase.php @@ -11,7 +11,7 @@ namespace Overblog\GraphQLBundle\Tests\Functional; -use Overblog\GraphQLBundle\Tests\Functional\app\AppKernel; +use Overblog\GraphQLBundle\Tests\Functional\App\TestKernel; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpFoundation\Request; @@ -27,15 +27,16 @@ abstract class TestCase extends WebTestCase const DEFAULT_PASSWORD = '123'; /** - * @var AppKernel[] + * @var TestKernel[] */ private static $kernels = []; + /** + * {@inheritdoc} + */ protected static function getKernelClass() { - require_once __DIR__.'/app/AppKernel.php'; - - return AppKernel::class; + return TestKernel::class; } /** @@ -79,13 +80,6 @@ protected function tearDown() static::$kernel = null; } - protected static function createAndBootKernel(array $options = []) - { - static::bootKernel($options); - - static::getContainer()->get('overblog_graphql.cache_compiler')->loadClasses(); - } - protected static function executeGraphQLRequest($query, $rootValue = [], $throwException = false) { $request = new Request(); diff --git a/Tests/Functional/Type/CustomScalarTest.php b/Tests/Functional/Type/CustomScalarTest.php index 7b20e5285..129ad3d40 100644 --- a/Tests/Functional/Type/CustomScalarTest.php +++ b/Tests/Functional/Type/CustomScalarTest.php @@ -19,7 +19,7 @@ protected function setUp() { parent::setUp(); - static::createAndBootKernel(['test_case' => 'customScalar']); + static::bootKernel(['test_case' => 'customScalar']); } public function testDateTimeTypeSerialize() diff --git a/Tests/Functional/Type/DefinitionTest.php b/Tests/Functional/Type/DefinitionTest.php index 6d58de02c..3ea60283f 100644 --- a/Tests/Functional/Type/DefinitionTest.php +++ b/Tests/Functional/Type/DefinitionTest.php @@ -22,7 +22,7 @@ protected function setUp() { parent::setUp(); - static::createAndBootKernel(['test_case' => 'definition']); + static::bootKernel(['test_case' => 'definition']); } public function testDefinesEnumTypeWithDeprecatedValue() diff --git a/Tests/Relay/Node/NodeFieldDefinitionTest.php b/Tests/Relay/Node/NodeFieldDefinitionTest.php index d98433ff7..e48c35796 100644 --- a/Tests/Relay/Node/NodeFieldDefinitionTest.php +++ b/Tests/Relay/Node/NodeFieldDefinitionTest.php @@ -11,6 +11,7 @@ namespace Overblog\GraphQLBundle\Tests\Relay\Node; +use Overblog\GraphQLBundle\GraphQL\Relay\Node\NodeFieldResolver; use Overblog\GraphQLBundle\Relay\Node\NodeFieldDefinition; class NodeFieldDefinitionTest extends \PHPUnit_Framework_TestCase @@ -62,7 +63,7 @@ public function testValidConfig($idFetcher, $idFetcherCallbackArg, $nodeInterfac 'description' => 'Fetches an object given its ID', 'type' => $nodeInterfaceType, 'args' => ['id' => ['type' => 'ID!', 'description' => 'The ID of an object']], - 'resolve' => '@=resolver(\'relay_node_field\', [args, context, info, idFetcherCallback('.$idFetcherCallbackArg.')])', + 'resolve' => '@=resolver(\''.addslashes(NodeFieldResolver::class).'\', [args, context, info, idFetcherCallback('.$idFetcherCallbackArg.')])', ]; $this->assertEquals($expected, $this->definition->toMappingDefinition($config)); diff --git a/Tests/Relay/Node/PluralIdentifyingRootFieldDefinitionTest.php b/Tests/Relay/Node/PluralIdentifyingRootFieldDefinitionTest.php index 6d29559df..81fff6b7e 100644 --- a/Tests/Relay/Node/PluralIdentifyingRootFieldDefinitionTest.php +++ b/Tests/Relay/Node/PluralIdentifyingRootFieldDefinitionTest.php @@ -11,6 +11,7 @@ namespace Overblog\GraphQLBundle\Tests\Relay\Node; +use Overblog\GraphQLBundle\GraphQL\Relay\Node\PluralIdentifyingRootFieldResolver; use Overblog\GraphQLBundle\Relay\Node\PluralIdentifyingRootFieldDefinition; class PluralIdentifyingRootFieldDefinitionTest extends \PHPUnit_Framework_TestCase @@ -106,7 +107,7 @@ public function testValidConfig($resolveSingleInput, $expectedResolveSingleInput $expected = [ 'type' => '[User]', 'args' => ['username' => ['type' => '[UserInput!]!']], - 'resolve' => '@=resolver(\'relay_plural_identifying_field\', [args[\'username\'], context, info, resolveSingleInputCallback('.$expectedResolveSingleInputCallbackArg.')])', + 'resolve' => '@=resolver(\''.addslashes(PluralIdentifyingRootFieldResolver::class).'\', [args[\'username\'], context, info, resolveSingleInputCallback('.$expectedResolveSingleInputCallbackArg.')])', ]; $this->assertEquals($expected, $this->definition->toMappingDefinition($config)); diff --git a/composer.json b/composer.json index 5c737e91a..0cf558943 100644 --- a/composer.json +++ b/composer.json @@ -32,10 +32,11 @@ "php": ">=5.5.9", "doctrine/doctrine-cache-bundle": "^1.2", "overblog/graphql-php-generator": "^0.4.1", - "symfony/expression-language": "^2.7 || ^3.0", - "symfony/framework-bundle": "^2.7 || ^3.0", - "symfony/options-resolver": "^2.7 || ^3.0", - "symfony/property-access": "^2.7 || ^3.0", + "symfony/cache": "^3.1", + "symfony/expression-language": "^2.8 || ^3.0", + "symfony/framework-bundle": "^2.8 || ^3.0", + "symfony/options-resolver": "^2.8 || ^3.0", + "symfony/property-access": "^2.8 || ^3.0", "webonyx/graphql-php": "^0.9.4" }, "suggest": { @@ -48,16 +49,16 @@ "react/promise": "^2.5", "sensio/framework-extra-bundle": "^3.0", "sllh/php-cs-fixer-styleci-bridge": "^1.5", - "symfony/asset": "^2.7 || ^3.0", - "symfony/browser-kit": "^2.7 || ^3.0", - "symfony/css-selector": "^2.7 || ^3.0", - "symfony/dependency-injection": "^2.7 || ^3.0", - "symfony/phpunit-bridge": "^2.7 || ^3.0", - "symfony/security-bundle": "^2.7 || ^3.0", - "symfony/templating": "^2.7 || ^3.0", - "symfony/twig-bundle": "^2.7 || ^3.0", - "symfony/web-profiler-bundle": "^2.7 || ^3.0", - "symfony/yaml": "^2.7 || ^3.0" + "symfony/asset": "^2.8 || ^3.0", + "symfony/browser-kit": "^2.8 || ^3.0", + "symfony/css-selector": "^2.8 || ^3.0", + "symfony/dependency-injection": "^2.8 || ^3.0", + "symfony/phpunit-bridge": "^2.8 || ^3.0", + "symfony/security-bundle": "^2.8 || ^3.0", + "symfony/templating": "^2.8 || ^3.0", + "symfony/twig-bundle": "^2.8 || ^3.0", + "symfony/web-profiler-bundle": "^2.8 || ^3.0", + "symfony/yaml": "^2.8 || ^3.0" }, "extra": { "branch-alias": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 482f7c971..dba67cb9f 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -30,7 +30,7 @@ - + From eda49f613271d2a97293adfbbdfd30c49e3401fb Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Mon, 1 May 2017 14:55:26 +0200 Subject: [PATCH 2/2] Add some alias to expression language functions --- .../AuthorizationExpressionProvider.php | 77 +++-------- .../ConfigExpressionProvider.php | 124 ++++-------------- .../DependencyInjection/Parameter.php | 27 ++++ .../DependencyInjection/Service.php | 27 ++++ .../ExpressionFunction/GraphQL/IsTypeOf.php | 27 ++++ .../ExpressionFunction/GraphQL/Mutation.php | 27 ++++ .../GraphQL/Relay/FromGlobalID.php | 31 +++++ .../GraphQL/Relay/GlobalID.php | 34 +++++ .../GraphQL/Relay/IdFetcherCallback.php | 31 +++++ .../Relay/MutateAndGetPayloadCallback.php | 31 +++++ .../Relay/ResolveSingleInputCallback.php | 31 +++++ .../ExpressionFunction/GraphQL/Resolver.php | 27 ++++ .../ExpressionFunction/NewObject.php | 27 ++++ .../Security/HasAnyPermission.php | 29 ++++ .../Security/HasAnyRole.php | 29 ++++ .../Security/HasPermission.php | 29 ++++ .../ExpressionFunction/Security/HasRole.php | 27 ++++ .../Security/IsAnonymous.php | 27 ++++ .../Security/IsAuthenticated.php | 27 ++++ .../Security/IsFullyAuthenticated.php | 27 ++++ .../Security/IsRememberMe.php | 27 ++++ .../doc/definitions/expression-language.md | 36 ++--- 22 files changed, 602 insertions(+), 177 deletions(-) create mode 100644 ExpressionLanguage/ExpressionFunction/DependencyInjection/Parameter.php create mode 100644 ExpressionLanguage/ExpressionFunction/DependencyInjection/Service.php create mode 100644 ExpressionLanguage/ExpressionFunction/GraphQL/IsTypeOf.php create mode 100644 ExpressionLanguage/ExpressionFunction/GraphQL/Mutation.php create mode 100644 ExpressionLanguage/ExpressionFunction/GraphQL/Relay/FromGlobalID.php create mode 100644 ExpressionLanguage/ExpressionFunction/GraphQL/Relay/GlobalID.php create mode 100644 ExpressionLanguage/ExpressionFunction/GraphQL/Relay/IdFetcherCallback.php create mode 100644 ExpressionLanguage/ExpressionFunction/GraphQL/Relay/MutateAndGetPayloadCallback.php create mode 100644 ExpressionLanguage/ExpressionFunction/GraphQL/Relay/ResolveSingleInputCallback.php create mode 100644 ExpressionLanguage/ExpressionFunction/GraphQL/Resolver.php create mode 100644 ExpressionLanguage/ExpressionFunction/NewObject.php create mode 100644 ExpressionLanguage/ExpressionFunction/Security/HasAnyPermission.php create mode 100644 ExpressionLanguage/ExpressionFunction/Security/HasAnyRole.php create mode 100644 ExpressionLanguage/ExpressionFunction/Security/HasPermission.php create mode 100644 ExpressionLanguage/ExpressionFunction/Security/HasRole.php create mode 100644 ExpressionLanguage/ExpressionFunction/Security/IsAnonymous.php create mode 100644 ExpressionLanguage/ExpressionFunction/Security/IsAuthenticated.php create mode 100644 ExpressionLanguage/ExpressionFunction/Security/IsFullyAuthenticated.php create mode 100644 ExpressionLanguage/ExpressionFunction/Security/IsRememberMe.php diff --git a/ExpressionLanguage/AuthorizationExpressionProvider.php b/ExpressionLanguage/AuthorizationExpressionProvider.php index 4a68ed12b..990244396 100644 --- a/ExpressionLanguage/AuthorizationExpressionProvider.php +++ b/ExpressionLanguage/AuthorizationExpressionProvider.php @@ -11,6 +11,14 @@ namespace Overblog\GraphQLBundle\ExpressionLanguage; +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security\HasAnyPermission; +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security\HasAnyRole; +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security\HasPermission; +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security\HasRole; +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security\IsAnonymous; +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security\IsAuthenticated; +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security\IsFullyAuthenticated; +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security\IsRememberMe; use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; class AuthorizationExpressionProvider implements ExpressionFunctionProviderInterface @@ -18,67 +26,14 @@ class AuthorizationExpressionProvider implements ExpressionFunctionProviderInter public function getFunctions() { return [ - new ExpressionFunction( - 'hasRole', - function ($role) { - return sprintf('$container->get(\'security.authorization_checker\')->isGranted(%s)', $role); - } - ), - - new ExpressionFunction( - 'hasAnyRole', - function ($roles) { - $code = sprintf('array_reduce(%s, function ($isGranted, $role) use ($container) { return $isGranted || $container->get(\'security.authorization_checker\')->isGranted($role); }, false)', $roles); - - return $code; - } - ), - - new ExpressionFunction( - 'isAnonymous', - function () { - return '$container->get(\'security.authorization_checker\')->isGranted(\'IS_AUTHENTICATED_ANONYMOUSLY\')'; - } - ), - - new ExpressionFunction( - 'isRememberMe', - function () { - return '$container->get(\'security.authorization_checker\')->isGranted(\'IS_AUTHENTICATED_REMEMBERED\')'; - } - ), - - new ExpressionFunction( - 'isFullyAuthenticated', - function () { - return '$container->get(\'security.authorization_checker\')->isGranted(\'IS_AUTHENTICATED_FULLY\')'; - } - ), - - new ExpressionFunction( - 'isAuthenticated', - function () { - return '$container->get(\'security.authorization_checker\')->isGranted(\'IS_AUTHENTICATED_REMEMBERED\') || $container->get(\'security.authorization_checker\')->isGranted(\'IS_AUTHENTICATED_FULLY\')'; - } - ), - - new ExpressionFunction( - 'hasPermission', - function ($object, $permission) { - $code = sprintf('$container->get(\'security.authorization_checker\')->isGranted(%s, %s)', $permission, $object); - - return $code; - } - ), - - new ExpressionFunction( - 'hasAnyPermission', - function ($object, $permissions) { - $code = sprintf('array_reduce(%s, function ($isGranted, $permission) use ($container, $object) { return $isGranted || $container->get(\'security.authorization_checker\')->isGranted($permission, %s); }, false)', $permissions, $object); - - return $code; - } - ), + new HasRole(), + new HasAnyRole(), + new IsAnonymous(), + new IsRememberMe(), + new IsFullyAuthenticated(), + new IsAuthenticated(), + new HasPermission(), + new HasAnyPermission(), ]; } } diff --git a/ExpressionLanguage/ConfigExpressionProvider.php b/ExpressionLanguage/ConfigExpressionProvider.php index e0dfa275f..091629b17 100644 --- a/ExpressionLanguage/ConfigExpressionProvider.php +++ b/ExpressionLanguage/ConfigExpressionProvider.php @@ -11,8 +11,17 @@ namespace Overblog\GraphQLBundle\ExpressionLanguage; -use Overblog\GraphQLBundle\Generator\TypeGenerator; -use Overblog\GraphQLBundle\Relay\Node\GlobalId; +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\DependencyInjection\Parameter; +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\DependencyInjection\Service; +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\GraphQL\IsTypeOf; +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\GraphQL\Mutation; +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\GraphQL\Relay\FromGlobalID; +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\GraphQL\Relay\GlobalID as GlobalIDFunction; +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\GraphQL\Relay\IdFetcherCallback; +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\GraphQL\Relay\MutateAndGetPayloadCallback; +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\GraphQL\Relay\ResolveSingleInputCallback; +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\GraphQL\Resolver; +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\NewObject; use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; class ConfigExpressionProvider implements ExpressionFunctionProviderInterface @@ -20,102 +29,21 @@ class ConfigExpressionProvider implements ExpressionFunctionProviderInterface public function getFunctions() { return [ - new ExpressionFunction( - 'service', - function ($value) { - return sprintf('$container->get(%s)', $value); - } - ), - - new ExpressionFunction( - 'parameter', - function ($value) { - return sprintf('$container->getParameter(%s)', $value); - } - ), - - new ExpressionFunction( - 'isTypeOf', - function ($className) { - return sprintf('($className = %s) && $value instanceof $className', $className); - } - ), - - new ExpressionFunction( - 'resolver', - function ($alias, $args = '[]') { - return sprintf('$container->get(\'overblog_graphql.resolver_resolver\')->resolve([%s, %s])', $alias, $args); - } - ), - - new ExpressionFunction( - 'mutateAndGetPayloadCallback', - function ($mutateAndGetPayload) { - $code = 'function ($value) use ('.TypeGenerator::USE_FOR_CLOSURES.', $args, $context, $info) { '; - $code .= 'return '.$mutateAndGetPayload.'; }'; - - return $code; - } - ), - - new ExpressionFunction( - 'idFetcherCallback', - function ($idFetcher) { - $code = 'function ($value) use ('.TypeGenerator::USE_FOR_CLOSURES.', $args, $context, $info) { '; - $code .= 'return '.$idFetcher.'; }'; - - return $code; - } - ), - - new ExpressionFunction( - 'resolveSingleInputCallback', - function ($resolveSingleInput) { - $code = 'function ($value) use ('.TypeGenerator::USE_FOR_CLOSURES.', $args, $context, $info) { '; - $code .= 'return '.$resolveSingleInput.'; }'; - - return $code; - } - ), - - new ExpressionFunction( - 'mutation', - function ($alias, $args = '[]') { - return sprintf('$container->get(\'overblog_graphql.mutation_resolver\')->resolve([%s, %s])', $alias, $args); - } - ), - - new ExpressionFunction( - 'globalId', - function ($id, $typeName = null) { - $typeNameEmpty = null === $typeName || '""' === $typeName || 'null' === $typeName || 'false' === $typeName; - - return sprintf( - '%s::toGlobalId(%s, %s)', - GlobalId::class, - sprintf($typeNameEmpty ? '$info->parentType->name' : '%s', $typeName), - $id - ); - } - ), - - new ExpressionFunction( - 'fromGlobalId', - function ($globalId) { - return sprintf( - '%s::fromGlobalId(%s)', - GlobalId::class, - $globalId - ); - } - ), - - new ExpressionFunction( - 'newObject', - function ($className, $args = '[]') { - return sprintf('(new \ReflectionClass(%s))->newInstanceArgs(%s)', $className, $args); - } - ), + new Service(), + new Service('serv'), + new Parameter(), + new Parameter('param'), + new IsTypeOf(), + new Resolver(), + new Resolver('res'), + new Mutation(), + new Mutation('mut'), + new MutateAndGetPayloadCallback(), + new IdFetcherCallback(), + new ResolveSingleInputCallback(), + new GlobalIDFunction(), + new FromGlobalID(), + new NewObject(), ]; } } diff --git a/ExpressionLanguage/ExpressionFunction/DependencyInjection/Parameter.php b/ExpressionLanguage/ExpressionFunction/DependencyInjection/Parameter.php new file mode 100644 index 000000000..4f622acc0 --- /dev/null +++ b/ExpressionLanguage/ExpressionFunction/DependencyInjection/Parameter.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\DependencyInjection; + +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction; + +final class Parameter extends ExpressionFunction +{ + public function __construct($name = 'parameter') + { + parent::__construct( + $name, + function ($value) { + return sprintf('$container->getParameter(%s)', $value); + } + ); + } +} diff --git a/ExpressionLanguage/ExpressionFunction/DependencyInjection/Service.php b/ExpressionLanguage/ExpressionFunction/DependencyInjection/Service.php new file mode 100644 index 000000000..ad8a72291 --- /dev/null +++ b/ExpressionLanguage/ExpressionFunction/DependencyInjection/Service.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\DependencyInjection; + +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction; + +final class Service extends ExpressionFunction +{ + public function __construct($name = 'service') + { + parent::__construct( + $name, + function ($value) { + return sprintf('$container->get(%s)', $value); + } + ); + } +} diff --git a/ExpressionLanguage/ExpressionFunction/GraphQL/IsTypeOf.php b/ExpressionLanguage/ExpressionFunction/GraphQL/IsTypeOf.php new file mode 100644 index 000000000..d913313ae --- /dev/null +++ b/ExpressionLanguage/ExpressionFunction/GraphQL/IsTypeOf.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\GraphQL; + +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction; + +final class IsTypeOf extends ExpressionFunction +{ + public function __construct($name = 'isTypeOf') + { + parent::__construct( + $name, + function ($className) { + return sprintf('($className = %s) && $value instanceof $className', $className); + } + ); + } +} diff --git a/ExpressionLanguage/ExpressionFunction/GraphQL/Mutation.php b/ExpressionLanguage/ExpressionFunction/GraphQL/Mutation.php new file mode 100644 index 000000000..4bc03c968 --- /dev/null +++ b/ExpressionLanguage/ExpressionFunction/GraphQL/Mutation.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\GraphQL; + +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction; + +final class Mutation extends ExpressionFunction +{ + public function __construct($name = 'mutation') + { + parent::__construct( + $name, + function ($alias, $args = '[]') { + return sprintf('$container->get(\'overblog_graphql.mutation_resolver\')->resolve([%s, %s])', $alias, $args); + } + ); + } +} diff --git a/ExpressionLanguage/ExpressionFunction/GraphQL/Relay/FromGlobalID.php b/ExpressionLanguage/ExpressionFunction/GraphQL/Relay/FromGlobalID.php new file mode 100644 index 000000000..bed5ab61f --- /dev/null +++ b/ExpressionLanguage/ExpressionFunction/GraphQL/Relay/FromGlobalID.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\GraphQL\Relay; + +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction; + +final class FromGlobalID extends ExpressionFunction +{ + public function __construct($name = 'fromGlobalId') + { + parent::__construct( + $name, + function ($globalId) { + return sprintf( + '%s::fromGlobalId(%s)', + \Overblog\GraphQLBundle\Relay\Node\GlobalId::class, + $globalId + ); + } + ); + } +} diff --git a/ExpressionLanguage/ExpressionFunction/GraphQL/Relay/GlobalID.php b/ExpressionLanguage/ExpressionFunction/GraphQL/Relay/GlobalID.php new file mode 100644 index 000000000..c5f1e301b --- /dev/null +++ b/ExpressionLanguage/ExpressionFunction/GraphQL/Relay/GlobalID.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\GraphQL\Relay; + +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction; + +final class GlobalID extends ExpressionFunction +{ + public function __construct($name = 'globalId') + { + parent::__construct( + $name, + function ($id, $typeName = null) { + $typeNameEmpty = null === $typeName || '""' === $typeName || 'null' === $typeName || 'false' === $typeName; + + return sprintf( + '%s::toGlobalId(%s, %s)', + \Overblog\GraphQLBundle\Relay\Node\GlobalId::class, + sprintf($typeNameEmpty ? '$info->parentType->name' : '%s', $typeName), + $id + ); + } + ); + } +} diff --git a/ExpressionLanguage/ExpressionFunction/GraphQL/Relay/IdFetcherCallback.php b/ExpressionLanguage/ExpressionFunction/GraphQL/Relay/IdFetcherCallback.php new file mode 100644 index 000000000..844254d9e --- /dev/null +++ b/ExpressionLanguage/ExpressionFunction/GraphQL/Relay/IdFetcherCallback.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\GraphQL\Relay; + +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction; +use Overblog\GraphQLBundle\Generator\TypeGenerator; + +final class IdFetcherCallback extends ExpressionFunction +{ + public function __construct($name = 'idFetcherCallback') + { + parent::__construct( + $name, + function ($idFetcher) { + $code = 'function ($value) use ('.TypeGenerator::USE_FOR_CLOSURES.', $args, $context, $info) { '; + $code .= 'return '.$idFetcher.'; }'; + + return $code; + } + ); + } +} diff --git a/ExpressionLanguage/ExpressionFunction/GraphQL/Relay/MutateAndGetPayloadCallback.php b/ExpressionLanguage/ExpressionFunction/GraphQL/Relay/MutateAndGetPayloadCallback.php new file mode 100644 index 000000000..5eb1afd18 --- /dev/null +++ b/ExpressionLanguage/ExpressionFunction/GraphQL/Relay/MutateAndGetPayloadCallback.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\GraphQL\Relay; + +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction; +use Overblog\GraphQLBundle\Generator\TypeGenerator; + +final class MutateAndGetPayloadCallback extends ExpressionFunction +{ + public function __construct($name = 'mutateAndGetPayloadCallback') + { + parent::__construct( + $name, + function ($mutateAndGetPayload) { + $code = 'function ($value) use ('.TypeGenerator::USE_FOR_CLOSURES.', $args, $context, $info) { '; + $code .= 'return '.$mutateAndGetPayload.'; }'; + + return $code; + } + ); + } +} diff --git a/ExpressionLanguage/ExpressionFunction/GraphQL/Relay/ResolveSingleInputCallback.php b/ExpressionLanguage/ExpressionFunction/GraphQL/Relay/ResolveSingleInputCallback.php new file mode 100644 index 000000000..b0a99e833 --- /dev/null +++ b/ExpressionLanguage/ExpressionFunction/GraphQL/Relay/ResolveSingleInputCallback.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\GraphQL\Relay; + +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction; +use Overblog\GraphQLBundle\Generator\TypeGenerator; + +final class ResolveSingleInputCallback extends ExpressionFunction +{ + public function __construct($name = 'resolveSingleInputCallback') + { + parent::__construct( + $name, + function ($resolveSingleInput) { + $code = 'function ($value) use ('.TypeGenerator::USE_FOR_CLOSURES.', $args, $context, $info) { '; + $code .= 'return '.$resolveSingleInput.'; }'; + + return $code; + } + ); + } +} diff --git a/ExpressionLanguage/ExpressionFunction/GraphQL/Resolver.php b/ExpressionLanguage/ExpressionFunction/GraphQL/Resolver.php new file mode 100644 index 000000000..f350d7642 --- /dev/null +++ b/ExpressionLanguage/ExpressionFunction/GraphQL/Resolver.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\GraphQL; + +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction; + +final class Resolver extends ExpressionFunction +{ + public function __construct($name = 'resolver') + { + parent::__construct( + $name, + function ($alias, $args = '[]') { + return sprintf('$container->get(\'overblog_graphql.resolver_resolver\')->resolve([%s, %s])', $alias, $args); + } + ); + } +} diff --git a/ExpressionLanguage/ExpressionFunction/NewObject.php b/ExpressionLanguage/ExpressionFunction/NewObject.php new file mode 100644 index 000000000..2876e03a9 --- /dev/null +++ b/ExpressionLanguage/ExpressionFunction/NewObject.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction; + +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction; + +final class NewObject extends ExpressionFunction +{ + public function __construct($name = 'newObject') + { + parent::__construct( + $name, + function ($className, $args = '[]') { + return sprintf('(new \ReflectionClass(%s))->newInstanceArgs(%s)', $className, $args); + } + ); + } +} diff --git a/ExpressionLanguage/ExpressionFunction/Security/HasAnyPermission.php b/ExpressionLanguage/ExpressionFunction/Security/HasAnyPermission.php new file mode 100644 index 000000000..5efb6f67c --- /dev/null +++ b/ExpressionLanguage/ExpressionFunction/Security/HasAnyPermission.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security; + +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction; + +final class HasAnyPermission extends ExpressionFunction +{ + public function __construct($name = 'hasAnyPermission') + { + parent::__construct( + $name, + function ($object, $permissions) { + $code = sprintf('array_reduce(%s, function ($isGranted, $permission) use ($container, $object) { return $isGranted || $container->get(\'security.authorization_checker\')->isGranted($permission, %s); }, false)', $permissions, $object); + + return $code; + } + ); + } +} diff --git a/ExpressionLanguage/ExpressionFunction/Security/HasAnyRole.php b/ExpressionLanguage/ExpressionFunction/Security/HasAnyRole.php new file mode 100644 index 000000000..6bc77ec50 --- /dev/null +++ b/ExpressionLanguage/ExpressionFunction/Security/HasAnyRole.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security; + +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction; + +final class HasAnyRole extends ExpressionFunction +{ + public function __construct($name = 'hasAnyRole') + { + parent::__construct( + $name, + function ($roles) { + $code = sprintf('array_reduce(%s, function ($isGranted, $role) use ($container) { return $isGranted || $container->get(\'security.authorization_checker\')->isGranted($role); }, false)', $roles); + + return $code; + } + ); + } +} diff --git a/ExpressionLanguage/ExpressionFunction/Security/HasPermission.php b/ExpressionLanguage/ExpressionFunction/Security/HasPermission.php new file mode 100644 index 000000000..9b6d8f3e0 --- /dev/null +++ b/ExpressionLanguage/ExpressionFunction/Security/HasPermission.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security; + +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction; + +final class HasPermission extends ExpressionFunction +{ + public function __construct($name = 'hasPermission') + { + parent::__construct( + $name, + function ($object, $permission) { + $code = sprintf('$container->get(\'security.authorization_checker\')->isGranted(%s, %s)', $permission, $object); + + return $code; + } + ); + } +} diff --git a/ExpressionLanguage/ExpressionFunction/Security/HasRole.php b/ExpressionLanguage/ExpressionFunction/Security/HasRole.php new file mode 100644 index 000000000..f9fc86002 --- /dev/null +++ b/ExpressionLanguage/ExpressionFunction/Security/HasRole.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security; + +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction; + +final class HasRole extends ExpressionFunction +{ + public function __construct($name = 'hasRole') + { + parent::__construct( + $name, + function ($role) { + return sprintf('$container->get(\'security.authorization_checker\')->isGranted(%s)', $role); + } + ); + } +} diff --git a/ExpressionLanguage/ExpressionFunction/Security/IsAnonymous.php b/ExpressionLanguage/ExpressionFunction/Security/IsAnonymous.php new file mode 100644 index 000000000..aae0e7d2c --- /dev/null +++ b/ExpressionLanguage/ExpressionFunction/Security/IsAnonymous.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security; + +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction; + +final class IsAnonymous extends ExpressionFunction +{ + public function __construct($name = 'isAnonymous') + { + parent::__construct( + $name, + function () { + return '$container->get(\'security.authorization_checker\')->isGranted(\'IS_AUTHENTICATED_ANONYMOUSLY\')'; + } + ); + } +} diff --git a/ExpressionLanguage/ExpressionFunction/Security/IsAuthenticated.php b/ExpressionLanguage/ExpressionFunction/Security/IsAuthenticated.php new file mode 100644 index 000000000..79eb06dec --- /dev/null +++ b/ExpressionLanguage/ExpressionFunction/Security/IsAuthenticated.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security; + +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction; + +final class IsAuthenticated extends ExpressionFunction +{ + public function __construct($name = 'isAuthenticated') + { + parent::__construct( + $name, + function () { + return '$container->get(\'security.authorization_checker\')->isGranted(\'IS_AUTHENTICATED_REMEMBERED\') || $container->get(\'security.authorization_checker\')->isGranted(\'IS_AUTHENTICATED_FULLY\')'; + } + ); + } +} diff --git a/ExpressionLanguage/ExpressionFunction/Security/IsFullyAuthenticated.php b/ExpressionLanguage/ExpressionFunction/Security/IsFullyAuthenticated.php new file mode 100644 index 000000000..a759543a4 --- /dev/null +++ b/ExpressionLanguage/ExpressionFunction/Security/IsFullyAuthenticated.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security; + +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction; + +final class IsFullyAuthenticated extends ExpressionFunction +{ + public function __construct($name = 'isFullyAuthenticated') + { + parent::__construct( + $name, + function () { + return '$container->get(\'security.authorization_checker\')->isGranted(\'IS_AUTHENTICATED_FULLY\')'; + } + ); + } +} diff --git a/ExpressionLanguage/ExpressionFunction/Security/IsRememberMe.php b/ExpressionLanguage/ExpressionFunction/Security/IsRememberMe.php new file mode 100644 index 000000000..102199935 --- /dev/null +++ b/ExpressionLanguage/ExpressionFunction/Security/IsRememberMe.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security; + +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction; + +final class IsRememberMe extends ExpressionFunction +{ + public function __construct($name = 'isRememberMe') + { + parent::__construct( + $name, + function () { + return '$container->get(\'security.authorization_checker\')->isGranted(\'IS_AUTHENTICATED_REMEMBERED\')'; + } + ); + } +} diff --git a/Resources/doc/definitions/expression-language.md b/Resources/doc/definitions/expression-language.md index 64db2381d..d2248c45e 100644 --- a/Resources/doc/definitions/expression-language.md +++ b/Resources/doc/definitions/expression-language.md @@ -5,24 +5,24 @@ All definitions configs entries can use expression language but it must be expli **Functions description:** -Expression | Description | Usage ----------- | ----------- | ----- -object **service**(string $id) | Get a service from the container | @=service('my_service').customMethod() -mixed **parameter**(string $name) | Get parameter from the container | @=parameter('kernel.debug') -boolean **isTypeOf**(string $className) | Verified if `value` is instance of className | @=isTypeOf('AppBundle\\User\\User') -mixed **resolver**(string $alias, array $args = []) | call the method on the tagged service "overblog_graphql.resolver" with args | @=resolver('blog_by_id', [value['blogID']]) -mixed **mutation**(string $alias, array $args = []) | call the method on the tagged service "overblog_graphql.mutation" with args | @=mutation('remove_post_from_community', [value]) -string **globalId**(string\|int id, string $typeName = null) | Relay node globalId | @=globalId(15, 'User') -array **fromGlobalId**(string $globalId) | Relay node fromGlobalId | @=fromGlobalId('QmxvZzox') -object **newObject**(string $className, array $args = []) | Instantiation $className object with $args | @=newObject('AppBundle\\User\\User', ['John', 15]) -boolean **hasRole**(string $role) | Checks whether the token has a certain role. | @=hasRole('ROLE_API') -boolean **hasAnyRole**(string $role1, string $role2, ...string $roleN) | Checks whether the token has any of the given roles. | @=hasAnyRole('ROLE_API', 'ROLE_ADMIN') -boolean **isAnonymous**() | Checks whether the token is anonymous. | @=isAnonymous() -boolean **isRememberMe**() | Checks whether the token is remember me. | @=isRememberMe() -boolean **isFullyAuthenticated**() | Checks whether the token is fully authenticated. | @=isFullyAuthenticated() -boolean **isAuthenticated**() | Checks whether the token is not anonymous. | @=isAuthenticated() -boolean **hasPermission**(mixed $var, string $permission) | Checks whether the token has the given permission for the given object (requires the ACL system). |@=hasPermission(object, 'OWNER') -boolean **hasAnyPermission**(mixed $var, array $permissions) | Checks whether the token has any of the given permissions for the given object | @=hasAnyPermission(object, ['OWNER', 'ADMIN']) +Expression | Description | Usage | Alias +---------- | ----------- | ----- | ----- +object **service**(string $id) | Get a service from the container | @=service('my_service').customMethod() | serv +mixed **parameter**(string $name) | Get parameter from the container | @=parameter('kernel.debug') | param +boolean **isTypeOf**(string $className) | Verified if `value` is instance of className | @=isTypeOf('AppBundle\\User\\User') | +mixed **resolver**(string $alias, array $args = []) | call the method on the tagged service "overblog_graphql.resolver" with args | @=resolver('blog_by_id', [value['blogID']] | res +mixed **mutation**(string $alias, array $args = []) | call the method on the tagged service "overblog_graphql.mutation" with args | @=mutation('remove_post_from_community', [value]) | mut +string **globalId**(string\|int id, string $typeName = null) | Relay node globalId | @=globalId(15, 'User') | +array **fromGlobalId**(string $globalId) | Relay node fromGlobalId | @=fromGlobalId('QmxvZzox') | +object **newObject**(string $className, array $args = []) | Instantiation $className object with $args | @=newObject('AppBundle\\User\\User', ['John', 15]) | +boolean **hasRole**(string $role) | Checks whether the token has a certain role. | @=hasRole('ROLE_API') | +boolean **hasAnyRole**(string $role1, string $role2, ...string $roleN) | Checks whether the token has any of the given roles. | @=hasAnyRole('ROLE_API', 'ROLE_ADMIN') | +boolean **isAnonymous**() | Checks whether the token is anonymous. | @=isAnonymous() | +boolean **isRememberMe**() | Checks whether the token is remember me. | @=isRememberMe() | +boolean **isFullyAuthenticated**() | Checks whether the token is fully authenticated. | @=isFullyAuthenticated() | +boolean **isAuthenticated**() | Checks whether the token is not anonymous. | @=isAuthenticated() | +boolean **hasPermission**(mixed $var, string $permission) | Checks whether the token has the given permission for the given object (requires the ACL system). | @=hasPermission(object, 'OWNER') | +boolean **hasAnyPermission**(mixed $var, array $permissions) | Checks whether the token has any of the given permissions for the given object | @=hasAnyPermission(object, ['OWNER', 'ADMIN']) | **Variables description:**