Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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