diff --git a/Definition/Builder/TypeBuilder.php b/Definition/Builder/TypeBuilder.php index 13c1da74d..e88018ea6 100644 --- a/Definition/Builder/TypeBuilder.php +++ b/Definition/Builder/TypeBuilder.php @@ -18,6 +18,18 @@ class TypeBuilder { private $configResolver; + private $mapping = [ + 'relay-connection' => 'Overblog\\GraphQLBundle\\Relay\\Connection\\ConnectionType', + 'relay-node' => 'Overblog\\GraphQLBundle\\Relay\\Node\\NodeInterfaceType', + 'relay-mutation-input' => 'Overblog\\GraphQLBundle\\Relay\\Mutation\\InputType', + 'relay-mutation-payload' => 'Overblog\\GraphQLBundle\\Relay\\Mutation\\PayloadType', + 'object' => 'GraphQL\\Type\\Definition\\ObjectType', + 'enum' => 'GraphQL\\Type\\Definition\\EnumType', + 'interface' => 'GraphQL\\Type\\Definition\\InterfaceType', + 'union' => 'GraphQL\\Type\\Definition\\UnionType', + 'input-object' => 'GraphQL\\Type\\Definition\\InputObjectType', + ]; + public function __construct(ResolverInterface $configResolver) { $this->configResolver = $configResolver; @@ -38,38 +50,10 @@ public function create($type, array $config) private function getBaseClassName($type) { - switch ($type) { - case 'relay-connection': - $class = 'Overblog\\GraphQLBundle\\Relay\\Connection\\ConnectionType'; - break; - - case 'relay-node': - $class = 'Overblog\\GraphQLBundle\\Relay\\Node\\NodeInterfaceType'; - break; - - case 'relay-mutation-input': - $class = 'Overblog\\GraphQLBundle\\Relay\\Mutation\\InputType'; - break; - - case 'relay-mutation-payload': - $class = 'Overblog\\GraphQLBundle\\Relay\\Mutation\\PayloadType'; - break; - - case 'object': - case 'enum': - case 'interface': - case 'union': - $class = sprintf('GraphQL\\Type\\Definition\\%sType', ucfirst($type)); - break; - - case 'input-object': - $class = 'GraphQL\\Type\\Definition\\InputObjectType'; - break; - - default: - throw new \RuntimeException(sprintf('Type "%s" is not managed.', $type)); + if (!isset($this->mapping[$type])) { + throw new \RuntimeException(sprintf('Type "%s" is not managed.', $type)); } - return $class; + return $this->mapping[$type]; } } diff --git a/DependencyInjection/OverblogGraphQLExtension.php b/DependencyInjection/OverblogGraphQLExtension.php index 5016573cb..d2dc99c3c 100644 --- a/DependencyInjection/OverblogGraphQLExtension.php +++ b/DependencyInjection/OverblogGraphQLExtension.php @@ -12,20 +12,12 @@ namespace Overblog\GraphQLBundle\DependencyInjection; use Symfony\Component\Config\FileLocator; -use Symfony\Component\Config\Resource\FileResource; -use Symfony\Component\Config\Util\XmlUtils; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; -use Symfony\Component\DependencyInjection\Reference; -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 OverblogGraphQLExtension extends Extension +class OverblogGraphQLExtension extends Extension implements PrependExtensionInterface { public function load(array $configs, ContainerBuilder $container) { @@ -38,26 +30,34 @@ public function load(array $configs, ContainerBuilder $container) $configuration = $this->getConfiguration($configs, $container); $config = $this->processConfiguration($configuration, $configs); - if (isset($config['services'])) { - foreach ($config['services'] as $name => $id) { - $alias = sprintf('%s.%s', $this->getAlias(), $name); - $container->setAlias($alias, $id); - } - } + $this->setServicesAliases($config, $container); + $this->setSchemaBuilderArguments($config, $container); + $this->setSchemaArguments($config, $container); + $this->setErrorHandlerArguments($config, $container); + $this->setGraphiQLTemplate($config, $container); + } - $container->getDefinition($this->getAlias().'.schema_builder') - ->replaceArgument(1, $config['definitions']['config_validation']); + public function prepend(ContainerBuilder $container) + { + $configs = $container->getExtensionConfig($this->getAlias()); + $configs = $container->getParameterBag()->resolveValue($configs); + $configuration = $this->getConfiguration($configs, $container); + $config = $this->processConfiguration($configuration, $configs); - if (isset($config['definitions']['schema'])) { - $container - ->getDefinition($this->getAlias().'.schema') - ->replaceArgument(0, $config['definitions']['schema']['query']) - ->replaceArgument(1, $config['definitions']['schema']['mutation']) - ->replaceArgument(2, $config['definitions']['schema']['subscription']) - ->setPublic(true) - ; + /** @var OverblogGraphQLTypesExtension $typesExtension */ + $typesExtension = $container->getExtension($this->getAlias().'_types'); + $typesExtension->containerPrependExtensionConfig($config, $container); + } + + private function setGraphiQLTemplate(array $config, ContainerBuilder $container) + { + if (isset($config['templates']['graphiql'])) { + $container->setParameter('overblog_graphql.graphiql_template', $config['templates']['graphiql']); } + } + private function setErrorHandlerArguments(array $config, ContainerBuilder $container) + { if (isset($config['definitions']['internal_error_message'])) { $container ->getDefinition($this->getAlias().'.error_handler') @@ -65,160 +65,35 @@ public function load(array $configs, ContainerBuilder $container) ->setPublic(true) ; } - - if (isset($config['templates']['graphiql'])) { - $container->setParameter('overblog_graphql.graphiql_template', $config['templates']['graphiql']); - } - - // Types - $typesConfigs = $this->getTypesConfigs($config, $container); - $typesConfig = $this->processConfiguration(new TypesConfiguration(), $typesConfigs); - - if (!empty($typesConfig)) { - $builderId = $this->getAlias().'.type_builder'; - - foreach ($typesConfig as $name => $options) { - $customTypeId = sprintf('%s.definition.custom_%s_type', $this->getAlias(), $container->underscore($name)); - - $options['config']['name'] = $name; - - $container - ->setDefinition($customTypeId, new Definition('GraphQL\\Type\\Definition\\Type')) - ->setFactory([new Reference($builderId), 'create']) - ->setArguments([$options['type'], $options['config']]) - ->addTag($this->getAlias().'.type', ['alias' => $name]) - ; - } - } } - private function getTypesConfigs(array $config, ContainerBuilder $container) + private function setSchemaBuilderArguments(array $config, ContainerBuilder $container) { - $yamlParser = null; - $bundles = $container->getParameter('kernel.bundles'); - - $typesMappings = []; - $typesConfig = []; - - // 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'] - )); - } - - // auto detect from bundle - foreach ($bundles as $name => $class) { - $bundle = new \ReflectionClass($class); - $bundleDir = dirname($bundle->getFileName()); - - $configPath = $bundleDir.'/'.$this->getMappingResourceConfigDirectory(); - $params = $this->detectConfigFiles($container, $configPath); - - if (null === $params) { - continue; - } - - $typesMappings[] = $params; - } - - // treats mappings - foreach ($typesMappings as $params) { - /** @var SplFileInfo $file */ - foreach ($params['files'] as $file) { - switch ($params['type']) { - case 'yml': - if (null === $yamlParser) { - $yamlParser = new YamlParser(); - } - try { - $typesConfig = array_merge($typesConfig, $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); - } - break; - - case 'xml': - try { - //@todo fix xml validateSchema - $xml = XmlUtils::loadFile($file->getRealPath());//, array($this, 'validateSchema')); - foreach ($xml->documentElement->childNodes as $node) { - if (!$node instanceof \DOMElement) { - continue; - } - $values = XmlUtils::convertDomElementToArray($node); - if (!is_array($values)) { - continue; - } - $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); - } - break; - } - } - } - - $typesConfigs = [$typesConfig]; - - // TODO remove when types mapping 100% functional - if (isset($config['definitions']['types'])) { - $typesConfigs[] = $config['definitions']['types']; - } - - return $typesConfigs; + $container->getDefinition($this->getAlias().'.schema_builder') + ->replaceArgument(1, $config['definitions']['config_validation']); } - private function detectConfigFiles(ContainerBuilder $container, $configPath, $type = null) + private function setSchemaArguments(array $config, ContainerBuilder $container) { - // add the closest existing directory as a resource - $resource = $configPath; - while (!is_dir($resource)) { - $resource = dirname($resource); - } - $container->addResource(new FileResource($resource)); - - $extension = $this->getMappingResourceExtension(); - $finder = new Finder(); - - $types = null === $type ? ['yml', 'xml'] : [$type]; - - foreach ($types as $type) { - try { - $finder->files()->in($configPath)->name('*.'.$extension.'.'.$type); - } catch (\InvalidArgumentException $e) { - continue; - } - if (0 === $finder->count()) { - continue; - } - - return [ - 'type' => $type, - 'files' => $finder, - ]; + if (isset($config['definitions']['schema'])) { + $container + ->getDefinition($this->getAlias().'.schema') + ->replaceArgument(0, $config['definitions']['schema']['query']) + ->replaceArgument(1, $config['definitions']['schema']['mutation']) + ->replaceArgument(2, $config['definitions']['schema']['subscription']) + ->setPublic(true) + ; } - - return; } - private function getMappingResourceConfigDirectory() + private function setServicesAliases(array $config, ContainerBuilder $container) { - return 'Resources/config/graphql'; - } - - private function getMappingResourceExtension() - { - return 'types'; + if (isset($config['services'])) { + foreach ($config['services'] as $name => $id) { + $alias = sprintf('%s.%s', $this->getAlias(), $name); + $container->setAlias($alias, $id); + } + } } public function getAlias() diff --git a/DependencyInjection/OverblogGraphQLTypesExtension.php b/DependencyInjection/OverblogGraphQLTypesExtension.php new file mode 100644 index 000000000..ca1f04024 --- /dev/null +++ b/DependencyInjection/OverblogGraphQLTypesExtension.php @@ -0,0 +1,228 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +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\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; +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; + + public function load(array $configs, ContainerBuilder $container) + { + $configuration = $this->getConfiguration($configs, $container); + $config = $this->processConfiguration($configuration, $configs); + + $builderId = $this->getAliasPrefix().'.type_builder'; + + foreach ($config as $name => $options) { + $customTypeId = sprintf('%s.definition.custom_%s_type', $this->getAliasPrefix(), $container->underscore($name)); + + $options['config']['name'] = $name; + + $container + ->setDefinition($customTypeId, new Definition('GraphQL\\Type\\Definition\\Type')) + ->setFactory([new Reference($builderId), 'create']) + ->setArguments([$options['type'], $options['config']]) + ->addTag($this->getAliasPrefix().'.type', ['alias' => $name]) + ; + } + } + + public function containerPrependExtensionConfig(array $config, ContainerBuilder $container) + { + $typesMappings = array_merge( + $this->typesConfigsMappingFromConfig($config, $container), + $this->typesConfigsMappingFromBundles($container) + ); + + // treats mappings + foreach ($typesMappings as $params) { + $this->prependExtensionConfigFromFiles($params['type'], $params['files'], $container); + } + + // TODO remove when types mapping 100% functional + if (isset($config['definitions']['types'])) { + $container->prependExtensionConfig($this->getAlias(), $config['definitions']['types']); + } + } + + /** + * @param $type + * @param SplFileInfo[] $files + * @param ContainerBuilder $container + */ + private function prependExtensionConfigFromFiles($type, $files, ContainerBuilder $container) + { + $typesConfig = []; + /** @var SplFileInfo $file */ + foreach ($files as $file) { + $typesConfig = array_merge( + $typesConfig, + 'yml' === $type ? $this->typesConfigFromYml($file, $container) : $this->typesConfigFromXml($file, $container) + ); + } + + $container->prependExtensionConfig($this->getAlias(), $typesConfig); + } + + private function typesConfigFromXml(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); + if (!is_array($values)) { + continue; + } + $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; + } + + private function typesConfigFromYml(SplFileInfo $file, ContainerBuilder $container) + { + if (null === $this->yamlParser) { + $this->yamlParser = new YamlParser(); + } + + 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; + } + + 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'] + )); + } + + return $typesMappings; + } + + private function typesConfigsMappingFromBundles(ContainerBuilder $container) + { + $typesMappings = []; + $bundles = $container->getParameter('kernel.bundles'); + + // auto detect from bundle + foreach ($bundles as $name => $class) { + $bundle = new \ReflectionClass($class); + $bundleDir = dirname($bundle->getFileName()); + + $configPath = $bundleDir.'/'.$this->getMappingResourceConfigDirectory(); + $params = $this->detectConfigFiles($container, $configPath); + + if (null === $params) { + continue; + } + + $typesMappings[] = $params; + } + + return $typesMappings; + } + + private function detectConfigFiles(ContainerBuilder $container, $configPath, $type = null) + { + // add the closest existing directory as a resource + $resource = $configPath; + while (!is_dir($resource)) { + $resource = dirname($resource); + } + $container->addResource(new FileResource($resource)); + + $extension = $this->getMappingResourceExtension(); + $finder = new Finder(); + + $types = null === $type ? ['yml', 'xml'] : [$type]; + + foreach ($types as $type) { + try { + $finder->files()->in($configPath)->name('*.'.$extension.'.'.$type); + } catch (\InvalidArgumentException $e) { + continue; + } + if (0 === $finder->count()) { + continue; + } + + return [ + 'type' => $type, + 'files' => $finder, + ]; + } + + return; + } + + private function getMappingResourceConfigDirectory() + { + return 'Resources/config/graphql'; + } + + private function getMappingResourceExtension() + { + return 'types'; + } + + public function getAliasPrefix() + { + return 'overblog_graphql'; + } + + public function getAlias() + { + return $this->getAliasPrefix().'_types'; + } + + public function getConfiguration(array $config, ContainerBuilder $container) + { + return new TypesConfiguration(); + } +} diff --git a/OverblogGraphQLBundle.php b/OverblogGraphQLBundle.php index 471cb40fd..cd380719d 100644 --- a/OverblogGraphQLBundle.php +++ b/OverblogGraphQLBundle.php @@ -17,6 +17,7 @@ use Overblog\GraphQLBundle\DependencyInjection\Compiler\ResolverPass; use Overblog\GraphQLBundle\DependencyInjection\Compiler\TypePass; use Overblog\GraphQLBundle\DependencyInjection\OverblogGraphQLExtension; +use Overblog\GraphQLBundle\DependencyInjection\OverblogGraphQLTypesExtension; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -35,6 +36,7 @@ public function build(ContainerBuilder $container) $container->addCompilerPass(new ResolverPass()); $container->addCompilerPass(new MutationPass()); $container->addCompilerPass(new ArgPass()); + $container->registerExtension(new OverblogGraphQLTypesExtension()); } public function getContainerExtension()