Skip to content

Commit

Permalink
Merge pull request #234 from mcg-web/explicit_types_generation
Browse files Browse the repository at this point in the history
Explicit types generation
  • Loading branch information
mcg-web authored Nov 6, 2017
2 parents 5372ed5 + 5d4528e commit 2e34c8d
Show file tree
Hide file tree
Showing 16 changed files with 295 additions and 64 deletions.
33 changes: 33 additions & 0 deletions CacheWarmer/CompileCacheWarmer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Overblog\GraphQLBundle\CacheWarmer;

use Overblog\GraphQLBundle\Generator\TypeGenerator;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;

class CompileCacheWarmer implements CacheWarmerInterface
{
/** @var TypeGenerator */
private $typeGenerator;

public function __construct(TypeGenerator $typeGenerator)
{
$this->typeGenerator = $typeGenerator;
}

/**
* {@inheritdoc}
*/
public function isOptional()
{
return false;
}

/**
* {@inheritdoc}
*/
public function warmUp($cacheDir)
{
$this->typeGenerator->compile(TypeGenerator::MODE_WRITE | TypeGenerator::MODE_OVERRIDE);
}
}
45 changes: 45 additions & 0 deletions Command/CompileCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace Overblog\GraphQLBundle\Command;

use Overblog\GraphQLBundle\Generator\TypeGenerator;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\Output;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

class CompileCommand extends Command
{
private $typeGenerator;

public function __construct(TypeGenerator $typeGenerator)
{
parent::__construct();
$this->typeGenerator = $typeGenerator;
}

protected function configure()
{
$this
->setName('graphql:compile')
->setDescription('Generate types manually.')
;
}

protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln('<info>Types compilation starts</info>');
$classes = $this->typeGenerator->compile(TypeGenerator::MODE_WRITE | TypeGenerator::MODE_OVERRIDE);
$output->writeln('<info>Types compilation ends successfully</info>');
if ($output->getVerbosity() >= Output::VERBOSITY_VERBOSE) {
$io = new SymfonyStyle($input, $output);
$io->title('Summary');
$rows = [];
foreach ($classes as $class => $path) {
$rows[] = [$class, $path];
}
$io->table(['class', 'path'], $rows);
}
}
}
39 changes: 7 additions & 32 deletions DependencyInjection/Compiler/ConfigTypesPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,32 @@

namespace Overblog\GraphQLBundle\DependencyInjection\Compiler;

use Overblog\GraphQLBundle\Generator\TypeGenerator;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\DependencyInjection\Reference;

class ConfigTypesPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$config = $container->getParameter('overblog_graphql_types.config');
$generatedClasses = $container->get('overblog_graphql.cache_compiler')->compile(
$this->processConfig($config),
$container->getParameter('overblog_graphql.use_classloader_listener')
);
$generatedClasses = $container->get('overblog_graphql.cache_compiler')
->compile(TypeGenerator::MODE_MAPPING_ONLY);

foreach ($generatedClasses as $class => $file) {
if (!class_exists($class)) {
throw new \RuntimeException(sprintf(
'Type class %s not found. If you are using your own classLoader verify the path and the namespace please.',
json_encode($class))
);
}
$aliases = call_user_func($class.'::getAliases');
$aliases = [preg_replace('/Type$/', '', substr(strrchr($class, '\\'), 1))];
$this->setTypeServiceDefinition($container, $class, $aliases);
}
$container->getParameterBag()->remove('overblog_graphql_types.config');
}

private function setTypeServiceDefinition(ContainerBuilder $container, $class, array $aliases)
{
$definition = $container->setDefinition($class, new Definition($class));
$definition->setPublic(false);
$definition->setAutowired(true);
$definition->setArguments([new Reference('service_container')]);
foreach ($aliases as $alias) {
$definition->addTag('overblog_graphql.type', ['alias' => $alias]);
$definition->addTag('overblog_graphql.type', ['alias' => $alias, 'generated' => true]);
}
}

private function processConfig(array $configs)
{
return array_map(
function ($v) {
if (is_array($v)) {
return call_user_func([$this, 'processConfig'], $v);
} elseif (is_string($v) && 0 === strpos($v, '@=')) {
return new Expression(substr($v, 2));
}

return $v;
},
$configs
);
}
}
5 changes: 3 additions & 2 deletions DependencyInjection/Compiler/TaggedServiceMappingPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ private function getTaggedServiceMapping(ContainerBuilder $container, $tagName)
$serviceMapping = [];

$taggedServices = $container->findTaggedServiceIds($tagName);
$isType = TypeTaggedServiceMappingPass::TAG_NAME === $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);
$tag = array_merge($tag, ['id' => $id]);
Expand Down Expand Up @@ -62,7 +62,8 @@ function ($methodCall) {
$solutionDefinition->getMethodCalls()
);
if (
is_subclass_of($solutionDefinition->getClass(), ContainerAwareInterface::class)
empty($options['generated']) // false is consider as empty
&& is_subclass_of($solutionDefinition->getClass(), ContainerAwareInterface::class)
&& !in_array('setContainer', $methods)
) {
@trigger_error(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

class TypeTaggedServiceMappingPass extends TaggedServiceMappingPass
{
const TAG_NAME = 'overblog_graphql.type';

protected function getTagName()
{
return 'overblog_graphql.type';
return self::TAG_NAME;
}

protected function getResolverServiceID()
Expand Down
1 change: 1 addition & 0 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public function getConfigTreeBuilder()
->scalarNode('class_namespace')->defaultValue('Overblog\\GraphQLBundle\\__DEFINITIONS__')->end()
->scalarNode('cache_dir')->defaultValue($this->cacheDir.'/overblog/graphql-bundle/__definitions__')->end()
->booleanNode('use_classloader_listener')->defaultTrue()->end()
->booleanNode('auto_compile')->defaultTrue()->end()
->booleanNode('show_debug_info')->defaultFalse()->end()
->booleanNode('config_validation')->defaultValue($this->debug)->end()
->arrayNode('schema')
Expand Down
14 changes: 14 additions & 0 deletions DependencyInjection/OverblogGraphQLExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Overblog\GraphQLBundle\DependencyInjection;

use GraphQL\Type\Schema;
use Overblog\GraphQLBundle\CacheWarmer\CompileCacheWarmer;
use Overblog\GraphQLBundle\Config\TypeWithOutputFieldsDefinition;
use Overblog\GraphQLBundle\EventListener\ClassLoaderListener;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
Expand Down Expand Up @@ -39,6 +40,7 @@ public function load(array $configs, ContainerBuilder $container)
$this->setShowDebug($config, $container);
$this->setDefinitionParameters($config, $container);
$this->setClassLoaderListener($config, $container);
$this->setCompilerCacheWarmer($config, $container);

$container->setParameter($this->getAlias().'.resources_dir', realpath(__DIR__.'/../Resources'));
}
Expand Down Expand Up @@ -67,6 +69,18 @@ public function getConfiguration(array $config, ContainerBuilder $container)
);
}

private function setCompilerCacheWarmer(array $config, ContainerBuilder $container)
{
if ($config['definitions']['auto_compile']) {
$definition = $container->setDefinition(
CompileCacheWarmer::class,
new Definition(CompileCacheWarmer::class)
);
$definition->setArguments([new Reference($this->getAlias().'.cache_compiler')]);
$definition->addTag('kernel.cache_warmer', ['priority' => 50]);
}
}

private function setClassLoaderListener(array $config, ContainerBuilder $container)
{
$container->setParameter($this->getAlias().'.use_classloader_listener', $config['definitions']['use_classloader_listener']);
Expand Down
40 changes: 32 additions & 8 deletions Generator/TypeGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Overblog\GraphQLBundle\Definition\Argument;
use Overblog\GraphQLBundle\Error\UserWarning;
use Overblog\GraphQLGenerator\Generator\TypeGenerator as BaseTypeGenerator;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\Filesystem\Filesystem;

class TypeGenerator extends BaseTypeGenerator
Expand All @@ -17,12 +18,18 @@ class TypeGenerator extends BaseTypeGenerator

private $defaultResolver;

private $configs;

private $useClassMap = true;

private static $classMapLoaded = false;

public function __construct($classNamespace, array $skeletonDirs, $cacheDir, callable $defaultResolver)
public function __construct($classNamespace, array $skeletonDirs, $cacheDir, callable $defaultResolver, array $configs, $useClassMap = true)
{
$this->setCacheDir($cacheDir);
$this->defaultResolver = $defaultResolver;
$this->configs = $this->processConfigs($configs);
$this->useClassMap = $useClassMap;
parent::__construct($classNamespace, $skeletonDirs);
}

Expand Down Expand Up @@ -179,17 +186,17 @@ function ($childrenComplexity, $args = []) <closureUseStatements>{
return $code;
}

public function compile(array $configs, $loadClasses = true)
public function compile($mode)
{
$cacheDir = $this->getCacheDir();
if (file_exists($cacheDir)) {
$writeMode = $mode & self::MODE_WRITE;
if ($writeMode && file_exists($cacheDir)) {
$fs = new Filesystem();
$fs->remove($cacheDir);
}
$classes = $this->generateClasses($this->configs, $cacheDir, $mode);

$classes = $this->generateClasses($configs, $cacheDir, true);

if ($loadClasses) {
if ($writeMode && $this->useClassMap) {
$content = "<?php\nreturn ".var_export($classes, true).';';
// replaced hard-coding absolute path by __DIR__ (see https://github.com/overblog/GraphQLBundle/issues/167)
$content = str_replace(' => \''.$cacheDir, ' => __DIR__ . \'', $content);
Expand All @@ -204,8 +211,9 @@ public function compile(array $configs, $loadClasses = true)

public function loadClasses($forceReload = false)
{
if (!self::$classMapLoaded || $forceReload) {
$classes = require $this->getClassesMap();
if ($this->useClassMap && (!self::$classMapLoaded || $forceReload)) {
$classMapFile = $this->getClassesMap();
$classes = file_exists($classMapFile) ? require $classMapFile : [];
/** @var ClassLoader $mapClassLoader */
static $mapClassLoader = null;
if (null === $mapClassLoader) {
Expand All @@ -225,4 +233,20 @@ private function getClassesMap()
{
return $this->getCacheDir().'/__classes.map';
}

private function processConfigs(array $configs)
{
return array_map(
function ($v) {
if (is_array($v)) {
return call_user_func([$this, 'processConfigs'], $v);
} elseif (is_string($v) && 0 === strpos($v, '@=')) {
return new Expression(substr($v, 2));
}

return $v;
},
$configs
);
}
}
27 changes: 26 additions & 1 deletion Resolver/TypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,14 @@ private function string2Type($alias)

private function baseType($alias)
{
$type = $this->getSolution($alias);
try {
$type = $this->getSolution($alias);
} catch (\Error $error) {
throw self::createTypeLoadingException($alias, $error);
} catch (\Exception $exception) {
throw self::createTypeLoadingException($alias, $exception);
}

if (null !== $type) {
return $type;
}
Expand Down Expand Up @@ -88,6 +95,24 @@ private function hasNeedListOfWrapper($alias)
return false;
}

/**
* @param string $alias
* @param \Throwable $errorOrException
*
* @return \RuntimeException
*/
private static function createTypeLoadingException($alias, $errorOrException)
{
return new \RuntimeException(
sprintf(
'Type class for alias %s could not be load. If you are using your own classLoader verify the path and the namespace please.',
json_encode($alias)
),
0,
$errorOrException
);
}

protected function postLoadSolution($solution)
{
// also add solution with real type name if needed for typeLoader when using autoMapping
Expand Down
12 changes: 10 additions & 2 deletions Resources/config/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ services:
class: Overblog\GraphQLBundle\Request\Parser
public: true



overblog_graphql.request_batch_parser:
class: Overblog\GraphQLBundle\Request\BatchParser

Expand Down Expand Up @@ -97,6 +95,8 @@ services:
- ["%overblog_graphql.resources_dir%/skeleton"]
- "%overblog_graphql.cache_dir%"
- "%overblog_graphql.default_resolver%"
- "%overblog_graphql_types.config%"
- "%overblog_graphql.use_classloader_listener%"
calls:
- ["addUseStatement", ["Symfony\\Component\\DependencyInjection\\ContainerInterface"]]
- ["addUseStatement", ["Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface"]]
Expand Down Expand Up @@ -151,3 +151,11 @@ services:
- "@overblog_graphql.resolver_resolver"
tags:
- { name: console.command }

overblog_graphql.command.compile:
class: Overblog\GraphQLBundle\Command\CompileCommand
public: true
arguments:
- "@overblog_graphql.cache_compiler"
tags:
- { name: console.command }
3 changes: 3 additions & 0 deletions Resources/doc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ overblog_graphql:
definitions:
# disable listener the bundle out of box classLoader
use_classloader_listener: false
# change to "false" to disable auto compilation.
# To generate types manually, see "graphql:compile" command.
auto_compile: true
# change classes cache dir (recommends using a directory that will be committed)
cache_dir: "/my/path/to/my/generated/classes"
# Can also change the namespace
Expand Down
Loading

0 comments on commit 2e34c8d

Please sign in to comment.