Skip to content

Commit 2e34c8d

Browse files
authored
Merge pull request #234 from mcg-web/explicit_types_generation
Explicit types generation
2 parents 5372ed5 + 5d4528e commit 2e34c8d

File tree

16 files changed

+295
-64
lines changed

16 files changed

+295
-64
lines changed

CacheWarmer/CompileCacheWarmer.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace Overblog\GraphQLBundle\CacheWarmer;
4+
5+
use Overblog\GraphQLBundle\Generator\TypeGenerator;
6+
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
7+
8+
class CompileCacheWarmer implements CacheWarmerInterface
9+
{
10+
/** @var TypeGenerator */
11+
private $typeGenerator;
12+
13+
public function __construct(TypeGenerator $typeGenerator)
14+
{
15+
$this->typeGenerator = $typeGenerator;
16+
}
17+
18+
/**
19+
* {@inheritdoc}
20+
*/
21+
public function isOptional()
22+
{
23+
return false;
24+
}
25+
26+
/**
27+
* {@inheritdoc}
28+
*/
29+
public function warmUp($cacheDir)
30+
{
31+
$this->typeGenerator->compile(TypeGenerator::MODE_WRITE | TypeGenerator::MODE_OVERRIDE);
32+
}
33+
}

Command/CompileCommand.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace Overblog\GraphQLBundle\Command;
4+
5+
use Overblog\GraphQLBundle\Generator\TypeGenerator;
6+
use Symfony\Component\Console\Command\Command;
7+
use Symfony\Component\Console\Input\InputInterface;
8+
use Symfony\Component\Console\Output\Output;
9+
use Symfony\Component\Console\Output\OutputInterface;
10+
use Symfony\Component\Console\Style\SymfonyStyle;
11+
12+
class CompileCommand extends Command
13+
{
14+
private $typeGenerator;
15+
16+
public function __construct(TypeGenerator $typeGenerator)
17+
{
18+
parent::__construct();
19+
$this->typeGenerator = $typeGenerator;
20+
}
21+
22+
protected function configure()
23+
{
24+
$this
25+
->setName('graphql:compile')
26+
->setDescription('Generate types manually.')
27+
;
28+
}
29+
30+
protected function execute(InputInterface $input, OutputInterface $output)
31+
{
32+
$output->writeln('<info>Types compilation starts</info>');
33+
$classes = $this->typeGenerator->compile(TypeGenerator::MODE_WRITE | TypeGenerator::MODE_OVERRIDE);
34+
$output->writeln('<info>Types compilation ends successfully</info>');
35+
if ($output->getVerbosity() >= Output::VERBOSITY_VERBOSE) {
36+
$io = new SymfonyStyle($input, $output);
37+
$io->title('Summary');
38+
$rows = [];
39+
foreach ($classes as $class => $path) {
40+
$rows[] = [$class, $path];
41+
}
42+
$io->table(['class', 'path'], $rows);
43+
}
44+
}
45+
}

DependencyInjection/Compiler/ConfigTypesPass.php

Lines changed: 7 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,57 +2,32 @@
22

33
namespace Overblog\GraphQLBundle\DependencyInjection\Compiler;
44

5+
use Overblog\GraphQLBundle\Generator\TypeGenerator;
56
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
67
use Symfony\Component\DependencyInjection\ContainerBuilder;
78
use Symfony\Component\DependencyInjection\Definition;
8-
use Symfony\Component\ExpressionLanguage\Expression;
9+
use Symfony\Component\DependencyInjection\Reference;
910

1011
class ConfigTypesPass implements CompilerPassInterface
1112
{
1213
public function process(ContainerBuilder $container)
1314
{
14-
$config = $container->getParameter('overblog_graphql_types.config');
15-
$generatedClasses = $container->get('overblog_graphql.cache_compiler')->compile(
16-
$this->processConfig($config),
17-
$container->getParameter('overblog_graphql.use_classloader_listener')
18-
);
15+
$generatedClasses = $container->get('overblog_graphql.cache_compiler')
16+
->compile(TypeGenerator::MODE_MAPPING_ONLY);
1917

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

3324
private function setTypeServiceDefinition(ContainerBuilder $container, $class, array $aliases)
3425
{
3526
$definition = $container->setDefinition($class, new Definition($class));
3627
$definition->setPublic(false);
37-
$definition->setAutowired(true);
28+
$definition->setArguments([new Reference('service_container')]);
3829
foreach ($aliases as $alias) {
39-
$definition->addTag('overblog_graphql.type', ['alias' => $alias]);
30+
$definition->addTag('overblog_graphql.type', ['alias' => $alias, 'generated' => true]);
4031
}
4132
}
42-
43-
private function processConfig(array $configs)
44-
{
45-
return array_map(
46-
function ($v) {
47-
if (is_array($v)) {
48-
return call_user_func([$this, 'processConfig'], $v);
49-
} elseif (is_string($v) && 0 === strpos($v, '@=')) {
50-
return new Expression(substr($v, 2));
51-
}
52-
53-
return $v;
54-
},
55-
$configs
56-
);
57-
}
5833
}

DependencyInjection/Compiler/TaggedServiceMappingPass.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ private function getTaggedServiceMapping(ContainerBuilder $container, $tagName)
1515
$serviceMapping = [];
1616

1717
$taggedServices = $container->findTaggedServiceIds($tagName);
18+
$isType = TypeTaggedServiceMappingPass::TAG_NAME === $tagName;
1819

1920
foreach ($taggedServices as $id => $tags) {
2021
$className = $container->findDefinition($id)->getClass();
21-
$isType = is_subclass_of($className, Type::class);
2222
foreach ($tags as $tag) {
2323
$this->checkRequirements($id, $tag);
2424
$tag = array_merge($tag, ['id' => $id]);
@@ -62,7 +62,8 @@ function ($methodCall) {
6262
$solutionDefinition->getMethodCalls()
6363
);
6464
if (
65-
is_subclass_of($solutionDefinition->getClass(), ContainerAwareInterface::class)
65+
empty($options['generated']) // false is consider as empty
66+
&& is_subclass_of($solutionDefinition->getClass(), ContainerAwareInterface::class)
6667
&& !in_array('setContainer', $methods)
6768
) {
6869
@trigger_error(

DependencyInjection/Compiler/TypeTaggedServiceMappingPass.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44

55
class TypeTaggedServiceMappingPass extends TaggedServiceMappingPass
66
{
7+
const TAG_NAME = 'overblog_graphql.type';
8+
79
protected function getTagName()
810
{
9-
return 'overblog_graphql.type';
11+
return self::TAG_NAME;
1012
}
1113

1214
protected function getResolverServiceID()

DependencyInjection/Configuration.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public function getConfigTreeBuilder()
4848
->scalarNode('class_namespace')->defaultValue('Overblog\\GraphQLBundle\\__DEFINITIONS__')->end()
4949
->scalarNode('cache_dir')->defaultValue($this->cacheDir.'/overblog/graphql-bundle/__definitions__')->end()
5050
->booleanNode('use_classloader_listener')->defaultTrue()->end()
51+
->booleanNode('auto_compile')->defaultTrue()->end()
5152
->booleanNode('show_debug_info')->defaultFalse()->end()
5253
->booleanNode('config_validation')->defaultValue($this->debug)->end()
5354
->arrayNode('schema')

DependencyInjection/OverblogGraphQLExtension.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Overblog\GraphQLBundle\DependencyInjection;
44

55
use GraphQL\Type\Schema;
6+
use Overblog\GraphQLBundle\CacheWarmer\CompileCacheWarmer;
67
use Overblog\GraphQLBundle\Config\TypeWithOutputFieldsDefinition;
78
use Overblog\GraphQLBundle\EventListener\ClassLoaderListener;
89
use Symfony\Component\Cache\Adapter\ArrayAdapter;
@@ -39,6 +40,7 @@ public function load(array $configs, ContainerBuilder $container)
3940
$this->setShowDebug($config, $container);
4041
$this->setDefinitionParameters($config, $container);
4142
$this->setClassLoaderListener($config, $container);
43+
$this->setCompilerCacheWarmer($config, $container);
4244

4345
$container->setParameter($this->getAlias().'.resources_dir', realpath(__DIR__.'/../Resources'));
4446
}
@@ -67,6 +69,18 @@ public function getConfiguration(array $config, ContainerBuilder $container)
6769
);
6870
}
6971

72+
private function setCompilerCacheWarmer(array $config, ContainerBuilder $container)
73+
{
74+
if ($config['definitions']['auto_compile']) {
75+
$definition = $container->setDefinition(
76+
CompileCacheWarmer::class,
77+
new Definition(CompileCacheWarmer::class)
78+
);
79+
$definition->setArguments([new Reference($this->getAlias().'.cache_compiler')]);
80+
$definition->addTag('kernel.cache_warmer', ['priority' => 50]);
81+
}
82+
}
83+
7084
private function setClassLoaderListener(array $config, ContainerBuilder $container)
7185
{
7286
$container->setParameter($this->getAlias().'.use_classloader_listener', $config['definitions']['use_classloader_listener']);

Generator/TypeGenerator.php

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Overblog\GraphQLBundle\Definition\Argument;
88
use Overblog\GraphQLBundle\Error\UserWarning;
99
use Overblog\GraphQLGenerator\Generator\TypeGenerator as BaseTypeGenerator;
10+
use Symfony\Component\ExpressionLanguage\Expression;
1011
use Symfony\Component\Filesystem\Filesystem;
1112

1213
class TypeGenerator extends BaseTypeGenerator
@@ -17,12 +18,18 @@ class TypeGenerator extends BaseTypeGenerator
1718

1819
private $defaultResolver;
1920

21+
private $configs;
22+
23+
private $useClassMap = true;
24+
2025
private static $classMapLoaded = false;
2126

22-
public function __construct($classNamespace, array $skeletonDirs, $cacheDir, callable $defaultResolver)
27+
public function __construct($classNamespace, array $skeletonDirs, $cacheDir, callable $defaultResolver, array $configs, $useClassMap = true)
2328
{
2429
$this->setCacheDir($cacheDir);
2530
$this->defaultResolver = $defaultResolver;
31+
$this->configs = $this->processConfigs($configs);
32+
$this->useClassMap = $useClassMap;
2633
parent::__construct($classNamespace, $skeletonDirs);
2734
}
2835

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

182-
public function compile(array $configs, $loadClasses = true)
189+
public function compile($mode)
183190
{
184191
$cacheDir = $this->getCacheDir();
185-
if (file_exists($cacheDir)) {
192+
$writeMode = $mode & self::MODE_WRITE;
193+
if ($writeMode && file_exists($cacheDir)) {
186194
$fs = new Filesystem();
187195
$fs->remove($cacheDir);
188196
}
197+
$classes = $this->generateClasses($this->configs, $cacheDir, $mode);
189198

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

205212
public function loadClasses($forceReload = false)
206213
{
207-
if (!self::$classMapLoaded || $forceReload) {
208-
$classes = require $this->getClassesMap();
214+
if ($this->useClassMap && (!self::$classMapLoaded || $forceReload)) {
215+
$classMapFile = $this->getClassesMap();
216+
$classes = file_exists($classMapFile) ? require $classMapFile : [];
209217
/** @var ClassLoader $mapClassLoader */
210218
static $mapClassLoader = null;
211219
if (null === $mapClassLoader) {
@@ -225,4 +233,20 @@ private function getClassesMap()
225233
{
226234
return $this->getCacheDir().'/__classes.map';
227235
}
236+
237+
private function processConfigs(array $configs)
238+
{
239+
return array_map(
240+
function ($v) {
241+
if (is_array($v)) {
242+
return call_user_func([$this, 'processConfigs'], $v);
243+
} elseif (is_string($v) && 0 === strpos($v, '@=')) {
244+
return new Expression(substr($v, 2));
245+
}
246+
247+
return $v;
248+
},
249+
$configs
250+
);
251+
}
228252
}

Resolver/TypeResolver.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,14 @@ private function string2Type($alias)
4848

4949
private function baseType($alias)
5050
{
51-
$type = $this->getSolution($alias);
51+
try {
52+
$type = $this->getSolution($alias);
53+
} catch (\Error $error) {
54+
throw self::createTypeLoadingException($alias, $error);
55+
} catch (\Exception $exception) {
56+
throw self::createTypeLoadingException($alias, $exception);
57+
}
58+
5259
if (null !== $type) {
5360
return $type;
5461
}
@@ -88,6 +95,24 @@ private function hasNeedListOfWrapper($alias)
8895
return false;
8996
}
9097

98+
/**
99+
* @param string $alias
100+
* @param \Throwable $errorOrException
101+
*
102+
* @return \RuntimeException
103+
*/
104+
private static function createTypeLoadingException($alias, $errorOrException)
105+
{
106+
return new \RuntimeException(
107+
sprintf(
108+
'Type class for alias %s could not be load. If you are using your own classLoader verify the path and the namespace please.',
109+
json_encode($alias)
110+
),
111+
0,
112+
$errorOrException
113+
);
114+
}
115+
91116
protected function postLoadSolution($solution)
92117
{
93118
// also add solution with real type name if needed for typeLoader when using autoMapping

Resources/config/services.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ services:
3636
class: Overblog\GraphQLBundle\Request\Parser
3737
public: true
3838

39-
40-
4139
overblog_graphql.request_batch_parser:
4240
class: Overblog\GraphQLBundle\Request\BatchParser
4341

@@ -97,6 +95,8 @@ services:
9795
- ["%overblog_graphql.resources_dir%/skeleton"]
9896
- "%overblog_graphql.cache_dir%"
9997
- "%overblog_graphql.default_resolver%"
98+
- "%overblog_graphql_types.config%"
99+
- "%overblog_graphql.use_classloader_listener%"
100100
calls:
101101
- ["addUseStatement", ["Symfony\\Component\\DependencyInjection\\ContainerInterface"]]
102102
- ["addUseStatement", ["Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface"]]
@@ -151,3 +151,11 @@ services:
151151
- "@overblog_graphql.resolver_resolver"
152152
tags:
153153
- { name: console.command }
154+
155+
overblog_graphql.command.compile:
156+
class: Overblog\GraphQLBundle\Command\CompileCommand
157+
public: true
158+
arguments:
159+
- "@overblog_graphql.cache_compiler"
160+
tags:
161+
- { name: console.command }

0 commit comments

Comments
 (0)