Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make parsers configurable #1038

Closed
wants to merge 6 commits into from
Closed
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.idea
phpunit.xml
/build
/vendor
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ Documentation
- [Errors handling](docs/error-handling/index.md)
- [Events](docs/events/index.md)
- [Profiler](docs/profiler/index.md)
- [Tune configuration](docs/tune_configuration.md)

Talks and slides to help you start
----------------------------------
Expand Down
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
}
},
"config": {
"allow-plugins": {
"phpstan/extension-installer": true
},
"bin-dir": "bin",
"sort-packages": true
},
Expand Down
22 changes: 22 additions & 0 deletions docs/tune_configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Tune configuration
==================

Custom GraphQl configuration parsers
------------------------------------

You can configure custom GraphQl configuration parsers.
Your parsers MUST implement at least `\Overblog\GraphQLBundle\Config\Parser\ParserInterface`
and optionally `\Overblog\GraphQLBundle\Config\Parser\PreParserInterface` when required.

Default values will be applied when omitted.

```yaml
overblog_graphql:
# ...
parsers:
yaml: 'Overblog\GraphQLBundle\Config\Parser\YamlParser'
graphql: 'Overblog\GraphQLBundle\Config\Parser\GraphQLParser'
annotation: 'Overblog\GraphQLBundle\Config\Parser\AnnotationParser'
attribute: 'Overblog\GraphQLBundle\Config\Parser\AttributeParser'
# ...
```
61 changes: 44 additions & 17 deletions src/DependencyInjection/Compiler/ConfigParserPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Overblog\GraphQLBundle\Config\Parser\AnnotationParser;
use Overblog\GraphQLBundle\Config\Parser\AttributeParser;
use Overblog\GraphQLBundle\Config\Parser\GraphQLParser;
use Overblog\GraphQLBundle\Config\Parser\ParserInterface;
use Overblog\GraphQLBundle\Config\Parser\PreParserInterface;
use Overblog\GraphQLBundle\Config\Parser\YamlParser;
use Overblog\GraphQLBundle\DependencyInjection\TypesConfiguration;
Expand Down Expand Up @@ -35,24 +36,37 @@

class ConfigParserPass implements CompilerPassInterface
{
public const TYPE_YAML = 'yaml';
public const TYPE_GRAPHQL = 'graphql';
public const TYPE_ANNOTATION = 'annotation';
public const TYPE_ATTRIBUTE = 'attribute';

public const SUPPORTED_TYPES = [
self::TYPE_YAML,
self::TYPE_GRAPHQL,
self::TYPE_ANNOTATION,
self::TYPE_ATTRIBUTE,
];

public const SUPPORTED_TYPES_EXTENSIONS = [
'yaml' => '{yaml,yml}',
'graphql' => '{graphql,graphqls}',
'annotation' => 'php',
'attribute' => 'php',
self::TYPE_YAML => '{yaml,yml}',
self::TYPE_GRAPHQL => '{graphql,graphqls}',
self::TYPE_ANNOTATION => 'php',
self::TYPE_ATTRIBUTE => 'php',
];

/**
* @var array<string, class-string<PreParserInterface>>
* @deprecated They are going to be configurable.
* @var array<string, class-string<ParserInterface|PreParserInterface>>
*/
public const PARSERS = [
'yaml' => YamlParser::class,
'graphql' => GraphQLParser::class,
'annotation' => AnnotationParser::class,
'attribute' => AttributeParser::class,
self::TYPE_YAML => YamlParser::class,
self::TYPE_GRAPHQL => GraphQLParser::class,
self::TYPE_ANNOTATION => AnnotationParser::class,
self::TYPE_ATTRIBUTE => AttributeParser::class,
];

private static array $defaultDefaultConfig = [
private const DEFAULT_CONFIG = [
'definitions' => [
'mappings' => [
'auto_discover' => [
Expand All @@ -63,8 +77,15 @@ class ConfigParserPass implements CompilerPassInterface
'types' => [],
],
],
'parsers' => self::PARSERS,
];

/**
* @deprecated Use {@see ConfigParserPass::PARSERS }. Added for the backward compatibility.
* @var array<string,array<string,mixed>>
*/
private static array $defaultDefaultConfig = self::DEFAULT_CONFIG;

private array $treatedFiles = [];
private array $preTreatedFiles = [];

Expand All @@ -86,6 +107,10 @@ private function getConfigs(ContainerBuilder $container): array
$config = $container->getParameterBag()->resolveValue($container->getParameter('overblog_graphql.config'));
$container->getParameterBag()->remove('overblog_graphql.config');
$container->setParameter($this->getAlias().'.classes_map', []);

// use default value if needed
$config = array_replace_recursive(self::DEFAULT_CONFIG, $config);

$typesMappings = $this->mappingConfig($config, $container);
// reset treated files
$this->treatedFiles = [];
Expand All @@ -96,7 +121,7 @@ private function getConfigs(ContainerBuilder $container): array
// Pre-parse all files
AnnotationParser::reset($config);
AttributeParser::reset($config);
$typesNeedPreParsing = $this->typesNeedPreParsing();
$typesNeedPreParsing = $this->typesNeedPreParsing($config['parsers']);
foreach ($typesMappings as $params) {
if ($typesNeedPreParsing[$params['type']]) {
$this->parseTypeConfigFiles($params['type'], $params['files'], $container, $config, true);
Expand All @@ -115,10 +140,15 @@ private function getConfigs(ContainerBuilder $container): array
return $flattenTypeConfig;
}

private function typesNeedPreParsing(): array
/**
* @param array<string,string> $parsers
*
* @return array<string,bool>
*/
private function typesNeedPreParsing(array $parsers): array
{
$needPreParsing = [];
foreach (self::PARSERS as $type => $className) {
foreach ($parsers as $type => $className) {
$needPreParsing[$type] = is_a($className, PreParserInterface::class, true);
}

Expand All @@ -145,7 +175,7 @@ private function parseTypeConfigFiles(string $type, iterable $files, ContainerBu
continue;
}

$parser = [self::PARSERS[$type], $method];
$parser = [$configs['parsers'][$type], $method];
if (is_callable($parser)) {
$config[] = ($parser)($file, $container, $configs);
}
Expand All @@ -169,9 +199,6 @@ private function checkTypesDuplication(array $typeConfigs): void

private function mappingConfig(array $config, ContainerBuilder $container): array
{
// use default value if needed
$config = array_replace_recursive(self::$defaultDefaultConfig, $config);

$mappingConfig = $config['definitions']['mappings'];
$typesMappings = $mappingConfig['types'];

Expand Down
28 changes: 28 additions & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter;
use GraphQL\Validator\Rules\QueryComplexity;
use GraphQL\Validator\Rules\QueryDepth;
use Overblog\GraphQLBundle\Config\Parser\ParserInterface;
use Overblog\GraphQLBundle\Definition\Argument;
use Overblog\GraphQLBundle\DependencyInjection\Compiler\ConfigParserPass;
use Overblog\GraphQLBundle\Error\ErrorHandler;
Expand Down Expand Up @@ -57,6 +58,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->append($this->securitySection())
->append($this->doctrineSection())
->append($this->profilerSection())
->append($this->parsersSection())
->end();

return $treeBuilder;
Expand Down Expand Up @@ -318,6 +320,32 @@ private function doctrineSection(): ArrayNodeDefinition
return $node;
}

private function parsersSection(): ArrayNodeDefinition
{
/** @var ArrayNodeDefinition $node */
$node = (new TreeBuilder('parsers'))->getRootNode();
$node->useAttributeAsKey('name');

$parserPrototype = $node->scalarPrototype();
$parserPrototype->cannotBeEmpty();
$parserPrototype->validate()
->ifTrue(static function (string $x): bool {
return !is_subclass_of($x, ParserInterface::class, true);
})
->thenInvalid(sprintf('Parser MUST implement "%s', ParserInterface::class));

$node->validate()
->ifTrue(static function (array $x): bool {
return (bool) array_diff(array_keys($x), ConfigParserPass::SUPPORTED_TYPES);
})
->then(static function (array $x) {
$types = implode(', ', array_diff(array_keys($x), ConfigParserPass::SUPPORTED_TYPES));
throw new \InvalidArgumentException(sprintf('Configured parsers for not supported types: %s', $types));
});

return $node;
}

private function profilerSection(): ArrayNodeDefinition
{
$builder = new TreeBuilder('profiler');
Expand Down