From 0cfbf7d6fe176d356bbb5df8ad5b7290127527ab Mon Sep 17 00:00:00 2001
From: Jeremiah VALERIE <jeremiah@overblog.com>
Date: Sat, 15 May 2021 12:26:19 +0200
Subject: [PATCH 01/14] wip

---
 src/Config/InterfaceTypeDefinition.php        |   3 +-
 src/Config/ObjectTypeDefinition.php           |   3 +-
 src/Config/TypeDefinition.php                 |  88 ++++-
 src/Config/TypeWithOutputFieldsDefinition.php |   5 +-
 src/Config/UnionTypeDefinition.php            |   3 +-
 src/Generator/Config/AbstractConfig.php       |  48 +++
 src/Generator/Config/Arg.php                  |  25 ++
 src/Generator/Config/Callback.php             |  12 +
 src/Generator/Config/Config.php               |  50 +++
 src/Generator/Config/Field.php                |  50 +++
 src/Generator/Config/Validation.php           |  12 +
 .../Converter/ExpressionConverter.php         |   2 +-
 src/Generator/TypeBuilder.php                 | 326 ++++++++----------
 .../Compiler/ConfigParserPassTest.php         |  12 +-
 14 files changed, 446 insertions(+), 193 deletions(-)
 create mode 100644 src/Generator/Config/AbstractConfig.php
 create mode 100644 src/Generator/Config/Arg.php
 create mode 100644 src/Generator/Config/Callback.php
 create mode 100644 src/Generator/Config/Config.php
 create mode 100644 src/Generator/Config/Field.php
 create mode 100644 src/Generator/Config/Validation.php

diff --git a/src/Config/InterfaceTypeDefinition.php b/src/Config/InterfaceTypeDefinition.php
index 42e6dffb1..42d5133f6 100644
--- a/src/Config/InterfaceTypeDefinition.php
+++ b/src/Config/InterfaceTypeDefinition.php
@@ -12,13 +12,14 @@ public function getDefinition(): ArrayNodeDefinition
     {
         /** @var ArrayNodeDefinition $node */
         $node = self::createNode('_interface_config');
+        $this->resolverNormalization($node, 'typeResolver', 'resolveType');
 
         /** @phpstan-ignore-next-line */
         $node
             ->children()
                 ->append($this->nameSection())
                 ->append($this->outputFieldsSection())
-                ->append($this->resolveTypeSection())
+                ->append($this->resolverSection('typeResolver', 'GraphQL type resolver'))
                 ->append($this->descriptionSection())
                 ->arrayNode('interfaces')
                     ->prototype('scalar')->info('One of internal or custom interface types.')->end()
diff --git a/src/Config/ObjectTypeDefinition.php b/src/Config/ObjectTypeDefinition.php
index 8b4067bdc..41d06e469 100644
--- a/src/Config/ObjectTypeDefinition.php
+++ b/src/Config/ObjectTypeDefinition.php
@@ -16,6 +16,7 @@ public function getDefinition(): ArrayNodeDefinition
 
         /** @var ArrayNodeDefinition $node */
         $node = $builder->getRootNode();
+        $this->resolverNormalization($node, 'fieldResolver', 'resolveField');
 
         /** @phpstan-ignore-next-line */
         $node
@@ -29,7 +30,7 @@ public function getDefinition(): ArrayNodeDefinition
                     ->prototype('scalar')->info('One of internal or custom interface types.')->end()
                 ->end()
                 ->variableNode('isTypeOf')->end()
-                ->variableNode('resolveField')->end()
+                ->append($this->resolverSection('fieldResolver', 'GraphQL field value resolver'))
                 ->variableNode('fieldsDefaultAccess')
                     ->info('Default access control to fields (expression language can be use here)')
                 ->end()
diff --git a/src/Config/TypeDefinition.php b/src/Config/TypeDefinition.php
index f02588750..86df0c0b5 100644
--- a/src/Config/TypeDefinition.php
+++ b/src/Config/TypeDefinition.php
@@ -4,7 +4,9 @@
 
 namespace Overblog\GraphQLBundle\Config;
 
+use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionLanguage;
 use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
+use Symfony\Component\Config\Definition\Builder\NodeDefinition;
 use Symfony\Component\Config\Definition\Builder\ScalarNodeDefinition;
 use Symfony\Component\Config\Definition\Builder\TreeBuilder;
 use Symfony\Component\Config\Definition\Builder\VariableNodeDefinition;
@@ -32,11 +34,6 @@ public static function create(): self
         return new static();
     }
 
-    protected function resolveTypeSection(): VariableNodeDefinition
-    {
-        return self::createNode('resolveType', 'variable');
-    }
-
     protected function nameSection(): ScalarNodeDefinition
     {
         /** @var ScalarNodeDefinition $node */
@@ -156,6 +153,87 @@ protected function typeSection(bool $isRequired = false): ScalarNodeDefinition
         return $node;
     }
 
+    protected function resolverNormalization(NodeDefinition $node, string $new, string $old): void
+    {
+        $node
+            ->beforeNormalization()
+                ->ifTrue(fn ($options) => !empty($options[$old]) && empty($options[$new]))
+                ->then(function ($options) use ($old, $new) {
+                    if (is_callable($options[$old])) {
+                        if (is_array($options[$old])) {
+                            $options[$new]['method'] = implode('::', $options[$old]);
+                        } else {
+                            $options[$new]['method'] = $options[$old];
+                        }
+                    } elseif (is_string($options[$old])) {
+                        $options[$new]['expression'] = ExpressionLanguage::stringHasTrigger($options[$old]) ?
+                            ExpressionLanguage::unprefixExpression($options[$old]) :
+                            json_encode($options[$old]);
+                    } else {
+                        $options[$new]['expression'] = json_encode($options[$old]);
+                    }
+
+                    return $options;
+                })
+            ->end()
+            ->beforeNormalization()
+                ->ifTrue(fn ($options) => is_array($options) && array_key_exists($old, $options))
+                ->then(function ($options) use ($old) {
+                    unset($options[$old]);
+
+                    return $options;
+                })
+            ->end()
+            ->validate()
+                ->ifTrue(fn (array $v) => !empty($v[$new]) && !empty($v[$old]))
+                ->thenInvalid(sprintf(
+                    '"%s" and "%s" should not be use together in "%%s".',
+                    $new,
+                    $old,
+                ))
+            ->end()
+            ;
+    }
+
+    protected function resolverSection(string $name, string $info): ArrayNodeDefinition
+    {
+        /** @var ArrayNodeDefinition $node */
+        $node = self::createNode($name);
+        /** @phpstan-ignore-next-line */
+        $node
+            ->info($info)
+            ->validate()
+                ->ifTrue(fn (array $v) => !empty($v['method']) && !empty($v['expression']))
+                ->thenInvalid('"method" and "expression" should not be use together.')
+            ->end()
+            ->beforeNormalization()
+                // Allow short syntax
+                ->ifTrue(fn ($options) => is_string($options) && ExpressionLanguage::stringHasTrigger($options))
+                ->then(fn ($options) => ['expression' => ExpressionLanguage::unprefixExpression($options)])
+            ->end()
+            ->beforeNormalization()
+                ->ifTrue(fn ($options) => is_string($options) && !ExpressionLanguage::stringHasTrigger($options))
+                ->then(fn ($options) => ['method' => $options])
+            ->end()
+            ->beforeNormalization()
+                // clean expression
+                ->ifTrue(fn ($options) => isset($options['expression']) && is_string($options['expression']) && ExpressionLanguage::stringHasTrigger($options['expression']))
+                ->then(function ($options) {
+                    $options['expression'] = ExpressionLanguage::unprefixExpression($options['expression']);
+
+                    return $options;
+                })
+            ->end()
+            ->children()
+                ->scalarNode('method')->end()
+                ->scalarNode('expression')->end()
+                ->scalarNode('id')->end()
+            ->end()
+        ;
+
+        return $node;
+    }
+
     /**
      * @return mixed
      *
diff --git a/src/Config/TypeWithOutputFieldsDefinition.php b/src/Config/TypeWithOutputFieldsDefinition.php
index 438f8da3f..bb7ae544d 100644
--- a/src/Config/TypeWithOutputFieldsDefinition.php
+++ b/src/Config/TypeWithOutputFieldsDefinition.php
@@ -18,6 +18,7 @@ protected function outputFieldsSection(): NodeDefinition
         $node->isRequired()->requiresAtLeastOneElement();
 
         $prototype = $node->useAttributeAsKey('name', false)->prototype('array');
+        $this->resolverNormalization($prototype, 'resolver', 'resolve');
 
         /** @phpstan-ignore-next-line */
         $prototype
@@ -68,9 +69,7 @@ protected function outputFieldsSection(): NodeDefinition
                         ->end()
                     ->end()
                 ->end()
-                ->variableNode('resolve')
-                    ->info('Value resolver (expression language can be used here)')
-                ->end()
+                ->append($this->resolverSection('resolver', 'GraphQL value resolver'))
                 ->append($this->descriptionSection())
                 ->append($this->deprecationReasonSection())
                 ->variableNode('access')
diff --git a/src/Config/UnionTypeDefinition.php b/src/Config/UnionTypeDefinition.php
index 47c9d32da..fa48a922e 100644
--- a/src/Config/UnionTypeDefinition.php
+++ b/src/Config/UnionTypeDefinition.php
@@ -12,6 +12,7 @@ public function getDefinition(): ArrayNodeDefinition
     {
         /** @var ArrayNodeDefinition $node */
         $node = self::createNode('_union_config');
+        $this->resolverNormalization($node, 'typeResolver', 'resolveType');
 
         /** @phpstan-ignore-next-line */
         $node
@@ -24,7 +25,7 @@ public function getDefinition(): ArrayNodeDefinition
                     ->isRequired()
                     ->requiresAtLeastOneElement()
                 ->end()
-                ->append($this->resolveTypeSection())
+                ->append($this->resolverSection('typeResolver', 'GraphQL type resolver'))
                 ->append($this->descriptionSection())
             ->end();
 
diff --git a/src/Generator/Config/AbstractConfig.php b/src/Generator/Config/AbstractConfig.php
new file mode 100644
index 000000000..e8e6ef92a
--- /dev/null
+++ b/src/Generator/Config/AbstractConfig.php
@@ -0,0 +1,48 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Overblog\GraphQLBundle\Generator\Config;
+
+use InvalidArgumentException;
+
+/**
+ * @internal
+ */
+abstract class AbstractConfig
+{
+    protected const NORMALIZERS = [];
+
+    public function __construct(array $config)
+    {
+        $this->populate($config);
+    }
+
+    protected function populate(array $config): void
+    {
+        foreach ($config as $key => $value) {
+            $property = lcfirst(str_replace('_', '', ucwords($key, '_')));
+            $normalizer = static::NORMALIZERS[$property] ?? 'normalize'.ucfirst($property);
+            if (method_exists($this, $normalizer)) {
+                $this->$property = $this->$normalizer($value);
+            } elseif (property_exists($this, $property)) {
+                $this->$property = $value;
+            } else {
+                throw new InvalidArgumentException(sprintf('Unknown config "%s".', $property));
+            }
+        }
+    }
+
+    /**
+     * @param array|mixed $value
+     */
+    protected function normalizeCallback($value): Callback
+    {
+        return new Callback($value);
+    }
+
+    protected function normalizeValidation(array $config): Validation
+    {
+        return new Validation($config);
+    }
+}
diff --git a/src/Generator/Config/Arg.php b/src/Generator/Config/Arg.php
new file mode 100644
index 000000000..adb15d837
--- /dev/null
+++ b/src/Generator/Config/Arg.php
@@ -0,0 +1,25 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Overblog\GraphQLBundle\Generator\Config;
+
+final class Arg extends AbstractConfig
+{
+    public string $type;
+    public ?string $description = null;
+    public ?Validation $validation = null;
+
+    /**
+     * @var mixed
+     */
+    public $defaultValue;
+
+    public bool $hasDefaultValue;
+
+    public function __construct(array $config)
+    {
+        parent::__construct($config);
+        $this->hasDefaultValue = array_key_exists('defaultValue', $config);
+    }
+}
diff --git a/src/Generator/Config/Callback.php b/src/Generator/Config/Callback.php
new file mode 100644
index 000000000..a032b8224
--- /dev/null
+++ b/src/Generator/Config/Callback.php
@@ -0,0 +1,12 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Overblog\GraphQLBundle\Generator\Config;
+
+final class Callback extends AbstractConfig
+{
+    public ?string $method = null;
+    public ?string $expression = null;
+    public ?string $id = null;
+}
diff --git a/src/Generator/Config/Config.php b/src/Generator/Config/Config.php
new file mode 100644
index 000000000..41f693a05
--- /dev/null
+++ b/src/Generator/Config/Config.php
@@ -0,0 +1,50 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Overblog\GraphQLBundle\Generator\Config;
+
+/**
+ * @internal
+ */
+final class Config extends AbstractConfig
+{
+    protected const NORMALIZERS = [
+        'fieldResolver' => 'normalizeCallback',
+        'typeResolver' => 'normalizeCallback',
+//        'fieldsDefaultAccess' => 'normalizeCallback',
+//        'isTypeOf' => 'normalizeCallback',
+//        'fieldsDefaultPublic' => 'normalizeCallback',
+    ];
+
+    public string $name;
+    public ?string $description = null;
+    public string $className;
+    /** @var Field[]|null */
+    public ?array $fields = null;
+    public ?array $interfaces = null;
+    public ?Callback $fieldResolver = null;
+    public ?Callback $typeResolver = null;
+    public ?Validation $validation = null;
+    public ?array $builders = null;
+    public ?array $types = null;
+    public ?array $values = null;
+/** @var mixed|null */
+    /*?Callback*/ public $fieldsDefaultAccess = null;
+/** @var mixed|null */
+    /*?Callback*/ public $isTypeOf = null;
+/** @var mixed|null */
+    /*?Callback*/ public $fieldsDefaultPublic = null;
+    public ?string $scalarType = null;
+    /** @var callable|null */
+    public $serialize = null;
+    /** @var callable|null */
+    public $parseValue = null;
+    /** @var callable|null */
+    public $parseLiteral = null;
+
+    protected function normalizeFields(array $fields): array
+    {
+        return array_map(fn (array $field) => new Field($field), $fields);
+    }
+}
diff --git a/src/Generator/Config/Field.php b/src/Generator/Config/Field.php
new file mode 100644
index 000000000..f8b3703eb
--- /dev/null
+++ b/src/Generator/Config/Field.php
@@ -0,0 +1,50 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Overblog\GraphQLBundle\Generator\Config;
+
+final class Field extends AbstractConfig
+{
+    protected const NORMALIZERS = [
+        'resolver' => 'normalizeCallback',
+//        'access' => 'normalizeCallback',
+//        'public' => 'normalizeCallback',
+//        'complexity' => 'normalizeCallback',
+    ];
+
+    public string $type;
+    public ?string $description = null;
+    /** @var Arg[]|null */
+    public ?array $args = null;
+    public ?Callback $resolver = null;
+/** @var mixed|null */
+    /*?Callback*/ public $access = null;
+/** @var mixed|null */
+    /*?Callback*/ public $public = null;
+/** @var mixed|null */
+    /*?Callback*/ public $complexity = null;
+    public ?Validation $validation = null;
+    public ?array $validationGroups = null;
+    public ?string $deprecationReason = null;
+
+    /**
+     * @var mixed
+     */
+    public $defaultValue;
+
+    public bool $hasDefaultValue;
+    public bool $hasOnlyType;
+
+    public function __construct(array $config)
+    {
+        parent::__construct($config);
+        $this->hasOnlyType = 1 === count($config) && isset($config['type']);
+        $this->hasDefaultValue = array_key_exists('defaultValue', $config);
+    }
+
+    protected function normalizeArgs(array $args): array
+    {
+        return array_map(fn (array $arg) => new Arg($arg), $args);
+    }
+}
diff --git a/src/Generator/Config/Validation.php b/src/Generator/Config/Validation.php
new file mode 100644
index 000000000..fd2dc92de
--- /dev/null
+++ b/src/Generator/Config/Validation.php
@@ -0,0 +1,12 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Overblog\GraphQLBundle\Generator\Config;
+
+final class Validation extends AbstractConfig
+{
+    public ?array $constraints = null;
+    public ?string $link = null;
+    public ?array $cascade = null;
+}
diff --git a/src/Generator/Converter/ExpressionConverter.php b/src/Generator/Converter/ExpressionConverter.php
index 801558f1f..0b26f31d0 100644
--- a/src/Generator/Converter/ExpressionConverter.php
+++ b/src/Generator/Converter/ExpressionConverter.php
@@ -24,7 +24,7 @@ public function __construct(ExpressionLanguage $expressionLanguage)
     public function convert($value)
     {
         return $this->expressionLanguage->compile(
-            ExpressionLanguage::unprefixExpression($value),
+            ExpressionLanguage::stringHasTrigger($value) ? ExpressionLanguage::unprefixExpression($value) : $value,
             ExpressionLanguage::KNOWN_NAMES
         );
     }
diff --git a/src/Generator/TypeBuilder.php b/src/Generator/TypeBuilder.php
index d4ea8cf51..8434f30b2 100644
--- a/src/Generator/TypeBuilder.php
+++ b/src/Generator/TypeBuilder.php
@@ -28,12 +28,15 @@
 use Overblog\GraphQLBundle\Definition\Type\GeneratedTypeInterface;
 use Overblog\GraphQLBundle\Error\ResolveErrors;
 use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionLanguage as EL;
+use Overblog\GraphQLBundle\Generator\Config\Arg;
+use Overblog\GraphQLBundle\Generator\Config\Callback;
+use Overblog\GraphQLBundle\Generator\Config\Field;
+use Overblog\GraphQLBundle\Generator\Config\Validation;
 use Overblog\GraphQLBundle\Generator\Converter\ExpressionConverter;
 use Overblog\GraphQLBundle\Generator\Exception\GeneratorException;
 use Overblog\GraphQLBundle\Validator\InputValidator;
 use function array_map;
 use function class_exists;
-use function count;
 use function explode;
 use function in_array;
 use function is_array;
@@ -75,7 +78,7 @@ final class TypeBuilder
     private ExpressionConverter $expressionConverter;
     private PhpFile $file;
     private string $namespace;
-    private array $config;
+    private Config\Config $config;
     private string $type;
     private string $currentField;
     private string $gqlServices = '$'.TypeGenerator::GRAPHQL_SERVICES;
@@ -110,12 +113,12 @@ public function __construct(ExpressionConverter $expressionConverter, string $na
     public function build(array $config, string $type): PhpFile
     {
         // This values should be accessible from every method
-        $this->config = $config;
+        $this->config = new Config\Config($config);
         $this->type = $type;
 
         $this->file = PhpFile::new()->setNamespace($this->namespace);
 
-        $class = $this->file->createClass($config['class_name'])
+        $class = $this->file->createClass($this->config->className)
             ->setFinal()
             ->setExtends(static::EXTENDS[$type])
             ->addImplements(GeneratedTypeInterface::class, AliasedInterface::class)
@@ -127,7 +130,7 @@ public function build(array $config, string $type): PhpFile
         $class->createConstructor()
             ->addArgument('configProcessor', ConfigProcessor::class)
             ->addArgument(TypeGenerator::GRAPHQL_SERVICES, GraphQLServices::class)
-            ->append('$config = ', $this->buildConfig($config))
+            ->append('$config = ', $this->buildConfig())
             ->emptyLine()
             ->append('parent::__construct($configProcessor->process($config))');
 
@@ -224,7 +227,7 @@ private function wrapTypeRecursive($typeNode, bool &$isReference)
      *               $services->getType('PostInterface'),
      *               ...
      *           ],
-     *           'resolveField' => {@see buildResolveField},
+     *           'resolveField' => {@see buildResolver},
      *      ]
      *
      * Render example (input-object):
@@ -248,7 +251,7 @@ private function wrapTypeRecursive($typeNode, bool &$isReference)
      *              {@see buildField},
      *               ...
      *           ],
-     *          'resolveType' => {@see buildResolveType},
+     *          'resolveType' => {@see buildTypeResolver},
      *      ]
      *
      * Render example (union):
@@ -260,7 +263,7 @@ private function wrapTypeRecursive($typeNode, bool &$isReference)
      *              $services->getType('Photo'),
      *              ...
      *          ],
-     *          'resolveType' => {@see buildResolveType},
+     *          'resolveType' => {@see buildTypeResolver},
      *      ]
      *
      * Render example (custom-scalar):
@@ -290,69 +293,66 @@ private function wrapTypeRecursive($typeNode, bool &$isReference)
      *
      * @throws GeneratorException
      */
-    private function buildConfig(array $config): Collection
+    private function buildConfig(): Collection
     {
-        // Convert to an object for a better readability
-        $c = (object) $config;
-
         $configLoader = Collection::assoc();
         $configLoader->addItem('name', new Literal('self::NAME'));
 
-        if (isset($c->description)) {
-            $configLoader->addItem('description', $c->description);
+        if ($this->config->description) {
+            $configLoader->addItem('description', $this->config->description);
         }
 
         // only by input-object types (for class level validation)
-        if (isset($c->validation)) {
-            $configLoader->addItem('validation', $this->buildValidationRules($c->validation));
+        if (null !== $this->config->validation) {
+            $configLoader->addItem('validation', $this->buildValidationRules($this->config->validation));
         }
 
         // only by object, input-object and interface types
-        if (!empty($c->fields)) {
+        if (!empty($this->config->fields)) {
             $configLoader->addItem('fields', ArrowFunction::new(
-                Collection::map($c->fields, [$this, 'buildField'])
+                Collection::map($this->config->fields, [$this, 'buildField'])
             ));
         }
 
-        if (!empty($c->interfaces)) {
-            $items = array_map(fn ($type) => "$this->gqlServices->getType('$type')", $c->interfaces);
+        if (!empty($this->config->interfaces)) {
+            $items = array_map(fn ($type) => "$this->gqlServices->getType('$type')", $this->config->interfaces);
             $configLoader->addItem('interfaces', ArrowFunction::new(Collection::numeric($items, true)));
         }
 
-        if (!empty($c->types)) {
-            $items = array_map(fn ($type) => "$this->gqlServices->getType('$type')", $c->types);
+        if (!empty($this->config->types)) {
+            $items = array_map(fn ($type) => "$this->gqlServices->getType('$type')", $this->config->types);
             $configLoader->addItem('types', ArrowFunction::new(Collection::numeric($items, true)));
         }
 
-        if (isset($c->resolveType)) {
-            $configLoader->addItem('resolveType', $this->buildResolveType($c->resolveType));
+        if (null !== $this->config->typeResolver) {
+            $configLoader->addItem('resolveType', $this->buildTypeResolver($this->config->typeResolver));
         }
 
-        if (isset($c->resolveField)) {
-            $configLoader->addItem('resolveField', $this->buildResolve($c->resolveField));
+        if (null !== $this->config->fieldResolver) {
+            $configLoader->addItem('resolveField', $this->buildResolver($this->config->fieldResolver));
         }
 
         // only by enum types
-        if (isset($c->values)) {
-            $configLoader->addItem('values', Collection::assoc($c->values));
+        if (null !== $this->config->values) {
+            $configLoader->addItem('values', Collection::assoc($this->config->values));
         }
 
         // only by custom-scalar types
         if ('custom-scalar' === $this->type) {
-            if (isset($c->scalarType)) {
-                $configLoader->addItem('scalarType', $c->scalarType);
+            if ($this->config->scalarType) {
+                $configLoader->addItem('scalarType', $this->config->scalarType);
             }
 
-            if (isset($c->serialize)) {
-                $configLoader->addItem('serialize', $this->buildScalarCallback($c->serialize, 'serialize'));
+            if (null !== $this->config->serialize) {
+                $configLoader->addItem('serialize', $this->buildScalarCallback($this->config->serialize, 'serialize'));
             }
 
-            if (isset($c->parseValue)) {
-                $configLoader->addItem('parseValue', $this->buildScalarCallback($c->parseValue, 'parseValue'));
+            if (null !== $this->config->parseValue) {
+                $configLoader->addItem('parseValue', $this->buildScalarCallback($this->config->parseValue, 'parseValue'));
             }
 
-            if (isset($c->parseLiteral)) {
-                $configLoader->addItem('parseLiteral', $this->buildScalarCallback($c->parseLiteral, 'parseLiteral'));
+            if (null !== $this->config->parseLiteral) {
+                $configLoader->addItem('parseLiteral', $this->buildScalarCallback($this->config->parseLiteral, 'parseLiteral'));
             }
         }
 
@@ -388,7 +388,7 @@ private function buildScalarCallback($callback, string $fieldName)
 
         $className = Utils::resolveQualifier($class);
 
-        if ($className === $this->config['class_name']) {
+        if ($className === $this->config->className) {
             // Create an alias if name of serializer is same as type name
             $className = 'Base'.$className;
             $this->file->addUse($class, $className);
@@ -436,61 +436,57 @@ private function buildScalarCallback($callback, string $fieldName)
      *          return $services->mutation("create_post", $errors);
      *      }
      *
-     * @param mixed $resolve
-     *
      * @throws GeneratorException
      */
-    private function buildResolve($resolve, ?array $groups = null): GeneratorInterface
+    private function buildResolver(Callback $resolver, ?array $groups = null): ?GeneratorInterface
     {
-        if (is_callable($resolve) && is_array($resolve)) {
-            return Collection::numeric($resolve);
-        }
-
         // TODO: before creating an input validator, check if any validation rules are defined
-        if (EL::isStringWithTrigger($resolve)) {
-            $closure = Closure::new()
-                ->addArguments('value', 'args', 'context', 'info')
-                ->bindVar(TypeGenerator::GRAPHQL_SERVICES);
+        return $this->buildCallback(
+            $resolver,
+            ['value', 'args', 'context', 'info'],
+            function (string $expression) use ($groups) {
+                $closure = Closure::new()
+                    ->addArguments('value', 'args', 'context', 'info')
+                    ->bindVar(TypeGenerator::GRAPHQL_SERVICES);
 
-            $injectValidator = EL::expressionContainsVar('validator', $resolve);
+                $injectValidator = EL::expressionContainsVar('validator', $expression);
 
-            if ($this->configContainsValidation()) {
-                $injectErrors = EL::expressionContainsVar('errors', $resolve);
+                if ($this->configContainsValidation()) {
+                    $injectErrors = EL::expressionContainsVar('errors', $expression);
 
-                if ($injectErrors) {
-                    $closure->append('$errors = ', Instance::new(ResolveErrors::class));
-                }
+                    if ($injectErrors) {
+                        $closure->append('$errors = ', Instance::new(ResolveErrors::class));
+                    }
 
-                $closure->append('$validator = ', "$this->gqlServices->createInputValidator(...func_get_args())");
+                    $closure->append('$validator = ', "$this->gqlServices->createInputValidator(...func_get_args())");
 
-                // If auto-validation on or errors are injected
-                if (!$injectValidator || $injectErrors) {
-                    if (!empty($groups)) {
-                        $validationGroups = Collection::numeric($groups);
-                    } else {
-                        $validationGroups = 'null';
-                    }
+                    // If auto-validation on or errors are injected
+                    if (!$injectValidator || $injectErrors) {
+                        if (!empty($groups)) {
+                            $validationGroups = Collection::numeric($groups);
+                        } else {
+                            $validationGroups = 'null';
+                        }
 
-                    $closure->emptyLine();
+                        $closure->emptyLine();
 
-                    if ($injectErrors) {
-                        $closure->append('$errors->setValidationErrors($validator->validate(', $validationGroups, ', false))');
-                    } else {
-                        $closure->append('$validator->validate(', $validationGroups, ')');
-                    }
+                        if ($injectErrors) {
+                            $closure->append('$errors->setValidationErrors($validator->validate(', $validationGroups, ', false))');
+                        } else {
+                            $closure->append('$validator->validate(', $validationGroups, ')');
+                        }
 
-                    $closure->emptyLine();
+                        $closure->emptyLine();
+                    }
+                } elseif ($injectValidator) {
+                    throw new GeneratorException('Unable to inject an instance of the InputValidator. No validation constraints provided. Please remove the "validator" argument from the list of dependencies of your resolver or provide validation configs.');
                 }
-            } elseif ($injectValidator) {
-                throw new GeneratorException('Unable to inject an instance of the InputValidator. No validation constraints provided. Please remove the "validator" argument from the list of dependencies of your resolver or provide validation configs.');
-            }
-
-            $closure->append('return ', $this->expressionConverter->convert($resolve));
 
-            return $closure;
-        }
+                $closure->append('return ', $this->expressionConverter->convert($expression));
 
-        return ArrowFunction::new($resolve);
+                return $closure;
+            }
+        );
     }
 
     /**
@@ -498,14 +494,14 @@ private function buildResolve($resolve, ?array $groups = null): GeneratorInterfa
      */
     private function configContainsValidation(): bool
     {
-        $fieldConfig = $this->config['fields'][$this->currentField];
+        $fieldConfig = $this->config->fields[$this->currentField];
 
-        if (!empty($fieldConfig['validation'])) {
+        if (!empty($fieldConfig->validation)) {
             return true;
         }
 
-        foreach ($fieldConfig['args'] ?? [] as $argConfig) {
-            if (!empty($argConfig['validation'])) {
+        foreach ($fieldConfig->args ?? [] as $argConfig) {
+            if (!empty($argConfig->validation)) {
                 return true;
             }
         }
@@ -526,47 +522,38 @@ private function configContainsValidation(): bool
      *
      * If only constraints provided, uses {@see buildConstraints} directly.
      *
-     * @param array{
-     *     constraints: array,
-     *     link: string,
-     *     cascade: array
-     * } $config
-     *
      * @throws GeneratorException
      */
-    private function buildValidationRules(array $config): GeneratorInterface
+    private function buildValidationRules(Validation $validationConfig): GeneratorInterface
     {
-        // Convert to object for better readability
-        $c = (object) $config;
-
         $array = Collection::assoc();
 
-        if (!empty($c->link)) {
-            if (!str_contains($c->link, '::')) {
+        if (null !== $validationConfig->link) {
+            if (str_contains($validationConfig->link, '::')) {
                 // e.g. App\Entity\Droid
-                $array->addItem('link', $c->link);
+                $array->addItem('link', $validationConfig->link);
             } else {
                 // e.g. App\Entity\Droid::$id
-                $array->addItem('link', Collection::numeric($this->normalizeLink($c->link)));
+                $array->addItem('link', Collection::numeric($this->normalizeLink($validationConfig->link)));
             }
         }
 
-        if (isset($c->cascade)) {
-            // If there are only constarainst, use short syntax
-            if (empty($c->cascade['groups'])) {
+        if (null !== $validationConfig->cascade) {
+            // If there are only constraints, use short syntax
+            if (empty($validationConfig->cascade['groups'])) {
                 $this->file->addUse(InputValidator::class);
 
                 return Literal::new('InputValidator::CASCADE');
             }
-            $array->addItem('cascade', $c->cascade['groups']);
+            $array->addItem('cascade', $validationConfig->cascade['groups']);
         }
 
-        if (!empty($c->constraints)) {
-            // If there are only constarainst, use short syntax
+        if (!empty($validationConfig->constraints)) {
+            // If there are only constraints, use short syntax
             if (0 === $array->count()) {
-                return $this->buildConstraints($c->constraints);
+                return $this->buildConstraints($validationConfig->constraints);
             }
-            $array->addItem('constraints', $this->buildConstraints($c->constraints));
+            $array->addItem('constraints', $this->buildConstraints($validationConfig->constraints));
         }
 
         return $array;
@@ -659,84 +646,71 @@ private function buildConstraints(array $constraints = [], bool $inClosure = tru
      *              {@see buildArg},
      *               ...
      *           ],
-     *          'resolve' => {@see buildResolve},
+     *          'resolve' => {@see buildResolver},
      *          'complexity' => {@see buildComplexity},
      *      ]
      *
-     * @param array{
-     *     type:              string,
-     *     resolve?:          string,
-     *     description?:      string,
-     *     args?:             array,
-     *     complexity?:       string,
-     *     deprecatedReason?: string,
-     *     validation?:       array,
-     * } $fieldConfig
-     *
-     * @internal
+     * @return GeneratorInterface|Collection|string
      *
      * @throws GeneratorException
      *
-     * @return GeneratorInterface|Collection|string
+     * @internal
      */
-    public function buildField(array $fieldConfig, string $fieldname)
+    public function buildField(Field $fieldConfig, string $fieldName)
     {
-        $this->currentField = $fieldname;
-
-        // Convert to object for better readability
-        $c = (object) $fieldConfig;
+        $this->currentField = $fieldName;
 
         // If there is only 'type', use shorthand
-        if (1 === count($fieldConfig) && isset($c->type)) {
-            return $this->buildType($c->type);
+        if ($fieldConfig->hasOnlyType) {
+            return $this->buildType($fieldConfig->type);
         }
 
         $field = Collection::assoc()
-            ->addItem('type', $this->buildType($c->type));
+            ->addItem('type', $this->buildType($fieldConfig->type));
 
         // only for object types
-        if (isset($c->resolve)) {
-            if (isset($c->validation)) {
-                $field->addItem('validation', $this->buildValidationRules($c->validation));
+        if (!empty($fieldConfig->resolver)) {
+            if ($fieldConfig->validation) {
+                $field->addItem('validation', $this->buildValidationRules($fieldConfig->validation));
             }
-            $field->addItem('resolve', $this->buildResolve($c->resolve, $fieldConfig['validationGroups'] ?? null));
+            $field->addItem('resolve', $this->buildResolver($fieldConfig->resolver, $fieldConfig->validationGroups ?? null));
         }
 
-        if (isset($c->deprecationReason)) {
-            $field->addItem('deprecationReason', $c->deprecationReason);
+        if (null !== $fieldConfig->deprecationReason) {
+            $field->addItem('deprecationReason', $fieldConfig->deprecationReason);
         }
 
-        if (isset($c->description)) {
-            $field->addItem('description', $c->description);
+        if (null !== $fieldConfig->description) {
+            $field->addItem('description', $fieldConfig->description);
         }
 
-        if (!empty($c->args)) {
-            $field->addItem('args', Collection::map($c->args, [$this, 'buildArg'], false));
+        if (!empty($fieldConfig->args)) {
+            $field->addItem('args', Collection::map($fieldConfig->args, [$this, 'buildArg'], false));
         }
 
-        if (isset($c->complexity)) {
-            $field->addItem('complexity', $this->buildComplexity($c->complexity));
+        if (null !== $fieldConfig->complexity) {
+            $field->addItem('complexity', $this->buildComplexity($fieldConfig->complexity));
         }
 
-        if (isset($c->public)) {
-            $field->addItem('public', $this->buildPublic($c->public));
+        if (null !== $fieldConfig->public) {
+            $field->addItem('public', $this->buildPublic($fieldConfig->public));
         }
 
-        if (isset($c->access)) {
-            $field->addItem('access', $this->buildAccess($c->access));
+        if (null !== $fieldConfig->access) {
+            $field->addItem('access', $this->buildAccess($fieldConfig->access));
         }
 
-        if (!empty($c->access) && is_string($c->access) && EL::expressionContainsVar('object', $c->access)) {
+        if (!empty($fieldConfig->access) && is_string($fieldConfig->access) && EL::expressionContainsVar('object', $fieldConfig->access)) {
             $field->addItem('useStrictAccess', false);
         }
 
         if ('input-object' === $this->type) {
-            if (property_exists($c, 'defaultValue')) {
-                $field->addItem('defaultValue', $c->defaultValue);
+            if ($fieldConfig->hasDefaultValue) {
+                $field->addItem('defaultValue', $fieldConfig->defaultValue);
             }
 
-            if (isset($c->validation)) {
-                $field->addItem('validation', $this->buildValidationRules($c->validation));
+            if (null !== $fieldConfig->validation) {
+                $field->addItem('validation', $this->buildValidationRules($fieldConfig->validation));
             }
         }
 
@@ -754,39 +728,30 @@ public function buildField(array $fieldConfig, string $fieldname)
      *  ]
      * </code>
      *
-     * @param array{
-     *     type: string,
-     *     description?: string,
-     *     defaultValue?: string
-     * } $argConfig
-     *
      * @internal
      *
      * @throws GeneratorException
      */
-    public function buildArg(array $argConfig, string $argName): Collection
+    public function buildArg(Arg $argConfig, string $argName): Collection
     {
-        // Convert to object for better readability
-        $c = (object) $argConfig;
-
         $arg = Collection::assoc()
             ->addItem('name', $argName)
-            ->addItem('type', $this->buildType($c->type));
+            ->addItem('type', $this->buildType($argConfig->type));
 
-        if (isset($c->description)) {
-            $arg->addIfNotEmpty('description', $c->description);
+        if (null !== $argConfig->description) {
+            $arg->addIfNotEmpty('description', $argConfig->description);
         }
 
-        if (property_exists($c, 'defaultValue')) {
-            $arg->addItem('defaultValue', $c->defaultValue);
+        if ($argConfig->hasDefaultValue) {
+            $arg->addItem('defaultValue', $argConfig->defaultValue);
         }
 
-        if (!empty($c->validation)) {
-            if (in_array($c->type, self::BUILT_IN_TYPES) && isset($c->validation['cascade'])) {
+        if (!empty($argConfig->validation)) {
+            if (in_array($argConfig->type, self::BUILT_IN_TYPES) && null !== $argConfig->validation->cascade) {
                 throw new GeneratorException('Cascade validation cannot be applied to built-in types.');
             }
 
-            $arg->addIfNotEmpty('validation', $this->buildValidationRules($c->validation));
+            $arg->addIfNotEmpty('validation', $this->buildValidationRules($argConfig->validation));
         }
 
         return $arg;
@@ -902,22 +867,33 @@ private function buildAccess($access)
      * Render example:
      *
      *      fn($value, $context, $info) => $services->getType($value)
-     *
-     * @param mixed $resolveType
-     *
-     * @return mixed|ArrowFunction
      */
-    private function buildResolveType($resolveType)
+    private function buildTypeResolver(Callback $typeResolver): GeneratorInterface
     {
-        if (EL::isStringWithTrigger($resolveType)) {
-            $expression = $this->expressionConverter->convert($resolveType);
+        return $this->buildCallback($typeResolver, ['value', 'context', 'info']);
+    }
 
-            return ArrowFunction::new()
-                ->addArguments('value', 'context', 'info')
-                ->setExpression(Literal::new($expression));
+    protected function buildCallback(Callback $callback, array $argNames, ?callable $expressionBuilder = null): GeneratorInterface
+    {
+        if (null !== $callback->expression) {
+            if (null === $expressionBuilder) {
+                return ArrowFunction::new()
+                    ->addArguments(...$argNames)
+                    ->setExpression(Literal::new($this->expressionConverter->convert($callback->expression)))
+                    ;
+            } else {
+                return $expressionBuilder($callback->expression);
+            }
+        } elseif (null !== $callback->id) {
+            $fn = "$this->gqlServices->get('callbacks')->get('$callback->id')";
+            if ($callback->method) {
+                return Collection::numeric([$fn, $callback->method]);
+            } else {
+                return Literal::new($fn);
+            }
+        } else {
+            return Literal::new("'$callback->method'");
         }
-
-        return $resolveType;
     }
 
     /**
diff --git a/tests/DependencyInjection/Compiler/ConfigParserPassTest.php b/tests/DependencyInjection/Compiler/ConfigParserPassTest.php
index f5e6a78c2..8f9332955 100644
--- a/tests/DependencyInjection/Compiler/ConfigParserPassTest.php
+++ b/tests/DependencyInjection/Compiler/ConfigParserPassTest.php
@@ -243,22 +243,22 @@ public function testCustomBuilders(): void
                             'createdAt' => [
                                 'description' => 'The creation date of the object',
                                 'type' => 'Int!',
-                                'resolve' => '@=value.createdAt',
+                                'resolver' => ['expression' => 'value.createdAt'],
                             ],
                             'updatedAt' => [
                                 'description' => 'The update date of the object',
                                 'type' => 'Int!',
-                                'resolve' => '@=value.updatedAt',
+                                'resolver' => ['expression' => 'value.updatedAt'],
                             ],
                             'rawIDWithDescriptionOverride' => [
                                 'description' => 'rawIDWithDescriptionOverride description',
                                 'type' => 'Int!',
-                                'resolve' => '@=value.id',
+                                'resolver' => ['expression' => 'value.id'],
                             ],
                             'rawID' => [
                                 'description' => 'The raw ID of an object',
                                 'type' => 'Int!',
-                                'resolve' => '@=value.id',
+                                'resolver' => ['expression' => 'value.id'],
                             ],
                             'rawIDs' => [
                                 'type' => '[RawID!]!',
@@ -329,10 +329,10 @@ public function testCustomBuilders(): void
                         'fields' => [
                             'foo' => [
                                 'type' => 'FooPayload!',
-                                'resolve' => '@=mutation("Mutation.foo", args.input)',
                                 'args' => [
                                     'input' => ['type' => 'FooInput!'],
                                 ],
+                                'resolver' => ['expression' => 'mutation("Mutation.foo", args.input)'],
                             ],
                         ],
                         'name' => 'Mutation',
@@ -389,8 +389,8 @@ public function testCustomBuilders(): void
                     'decorator' => false,
                     'config' => [
                         'types' => ['FooSuccessPayload', 'FooFailurePayload'],
-                        'resolveType' => '@=query("PayloadTypeResolver", value, "FooSuccessPayload", "FooFailurePayload")',
                         'name' => 'FooPayload',
+                        'typeResolver' => ['expression' => 'query("PayloadTypeResolver", value, "FooSuccessPayload", "FooFailurePayload")'],
                     ],
                 ],
                 'FooSuccessPayload' => [

From a543004ce1bb8d64f072f28028c2b3ddba3b056d Mon Sep 17 00:00:00 2001
From: Jeremiah VALERIE <jeremiah@overblog.com>
Date: Sat, 15 May 2021 20:26:17 +0200
Subject: [PATCH 02/14] wip

---
 docs/definitions/expression-language.md       |  2 +-
 src/Definition/GraphQLServices.php            | 12 ++++++---
 .../Compiler/GraphQLServicesPass.php          | 27 ++++++++++---------
 .../DependencyInjection/Parameter.php         |  4 +--
 .../DependencyInjection/Service.php           |  4 +--
 .../ExpressionFunction/GraphQL/Arguments.php  |  4 +--
 .../ExpressionFunction/Security/GetUser.php   |  5 ++--
 .../Security/HasAnyPermission.php             |  5 ++--
 .../Security/HasAnyRole.php                   |  5 ++--
 .../Security/HasPermission.php                |  5 ++--
 .../ExpressionFunction/Security/HasRole.php   |  5 ++--
 .../Security/IsAnonymous.php                  |  5 ++--
 .../Security/IsAuthenticated.php              |  5 ++--
 .../Security/IsFullyAuthenticated.php         |  5 ++--
 .../ExpressionFunction/Security/IsGranted.php |  5 ++--
 .../Security/IsRememberMe.php                 |  5 ++--
 src/ExpressionLanguage/ExpressionLanguage.php | 22 ++++++++++++---
 src/Generator/TypeBuilder.php                 |  3 ++-
 .../Constraints/ExpressionValidator.php       |  8 +++---
 .../DependencyInjection/ParameterTest.php     |  5 ++--
 .../DependencyInjection/ServiceTest.php       |  5 ++--
 .../GraphQL/ArgumentsTest.php                 |  2 +-
 .../Security/GetUserTest.php                  | 11 +++-----
 .../Security/HasAnyPermissionTest.php         |  3 ++-
 .../Security/HasAnyRoleTest.php               |  3 ++-
 .../Security/HasPermissionTest.php            |  3 ++-
 .../Security/HasRoleTest.php                  |  3 ++-
 .../Security/IsAnonymousTest.php              |  3 ++-
 .../Security/IsAuthenticatedTest.php          |  3 ++-
 .../Security/IsFullyAuthenticatedTest.php     |  3 ++-
 .../Security/IsGrantedTest.php                |  3 ++-
 .../Security/IsRememberMeTest.php             |  3 ++-
 tests/ExpressionLanguage/TestCase.php         | 10 +++----
 33 files changed, 116 insertions(+), 80 deletions(-)

diff --git a/docs/definitions/expression-language.md b/docs/definitions/expression-language.md
index 8cbf79ae9..b4af60a58 100644
--- a/docs/definitions/expression-language.md
+++ b/docs/definitions/expression-language.md
@@ -386,7 +386,7 @@ MyType:
         fields:
             name:
                 type: String!
-                resolve: "@=service('my_private_service').formatName(value)"
+                resolve: "@=my_private_service.formatName(value)"
 ```
 
 To use a vendor private services:
diff --git a/src/Definition/GraphQLServices.php b/src/Definition/GraphQLServices.php
index 8e458a9aa..e3ff379cf 100644
--- a/src/Definition/GraphQLServices.php
+++ b/src/Definition/GraphQLServices.php
@@ -6,7 +6,11 @@
 
 use GraphQL\Type\Definition\ResolveInfo;
 use GraphQL\Type\Definition\Type;
+use Overblog\GraphQLBundle\Resolver\MutationResolver;
+use Overblog\GraphQLBundle\Resolver\QueryResolver;
+use Overblog\GraphQLBundle\Resolver\TypeResolver;
 use Overblog\GraphQLBundle\Validator\InputValidator;
+use Overblog\GraphQLBundle\Validator\InputValidatorFactory;
 use Symfony\Component\DependencyInjection\ServiceLocator;
 
 /**
@@ -21,7 +25,7 @@ final class GraphQLServices extends ServiceLocator
      */
     public function query(string $alias, ...$args)
     {
-        return $this->get('queryResolver')->resolve([$alias, $args]);
+        return $this->get(QueryResolver::class)->resolve([$alias, $args]);
     }
 
     /**
@@ -31,7 +35,7 @@ public function query(string $alias, ...$args)
      */
     public function mutation(string $alias, ...$args)
     {
-        return $this->get('mutationResolver')->resolve([$alias, $args]);
+        return $this->get(MutationResolver::class)->resolve([$alias, $args]);
     }
 
     /**
@@ -41,7 +45,7 @@ public function mutation(string $alias, ...$args)
      */
     public function getType(string $typeName): ?Type
     {
-        return $this->get('typeResolver')->resolve($typeName);
+        return $this->get(TypeResolver::class)->resolve($typeName);
     }
 
     /**
@@ -52,7 +56,7 @@ public function getType(string $typeName): ?Type
      */
     public function createInputValidator($value, ArgumentInterface $args, $context, ResolveInfo $info): InputValidator
     {
-        return $this->get('input_validator_factory')->create(
+        return $this->get(InputValidatorFactory::class)->create(
             new ResolverArgs($value, $args, $context, $info)
         );
     }
diff --git a/src/DependencyInjection/Compiler/GraphQLServicesPass.php b/src/DependencyInjection/Compiler/GraphQLServicesPass.php
index 4b88c48fa..56d63d706 100644
--- a/src/DependencyInjection/Compiler/GraphQLServicesPass.php
+++ b/src/DependencyInjection/Compiler/GraphQLServicesPass.php
@@ -6,7 +6,6 @@
 
 use InvalidArgumentException;
 use Overblog\GraphQLBundle\Definition\GraphQLServices;
-use Overblog\GraphQLBundle\Generator\TypeGenerator;
 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 use Symfony\Component\DependencyInjection\Reference;
@@ -27,26 +26,30 @@ public function process(ContainerBuilder $container): void
 
         foreach ($taggedServices as $id => $tags) {
             foreach ($tags as $attributes) {
-                if (empty($attributes['alias']) || !is_string($attributes['alias'])) {
-                    throw new InvalidArgumentException(
-                        sprintf('Service "%s" tagged "overblog_graphql.service" should have a valid "alias" attribute.', $id)
-                    );
-                }
-                $locateableServices[$attributes['alias']] = new Reference($id);
+                $locateableServices[] = new Reference($id);
+
+                if (array_key_exists('alias', $attributes)) {
+                    if (empty($attributes['alias']) || !is_string($attributes['alias'])) {
+                        throw new InvalidArgumentException(
+                            sprintf('Service "%s" tagged "overblog_graphql.service" should have a valid "alias" attribute.', $id)
+                        );
+                    }
 
-                $isPublic = !isset($attributes['public']) || $attributes['public'];
-                if ($isPublic) {
                     $expressionLanguageDefinition->addMethodCall(
-                        'addGlobalName',
+                        'addExpressionVariableNameServiceId',
                         [
-                            sprintf(TypeGenerator::GRAPHQL_SERVICES.'->get(\'%s\')', $attributes['alias']),
                             $attributes['alias'],
+                            $id,
                         ]
                     );
                 }
             }
         }
-        $locateableServices['container'] = new Reference('service_container');
+        $locateableServices[] = new Reference('service_container');
+        $expressionLanguageDefinition->addMethodCall(
+            'addExpressionVariableNameServiceId',
+            ['container', 'service_container']
+        );
 
         $container->findDefinition(GraphQLServices::class)->addArgument($locateableServices);
     }
diff --git a/src/ExpressionLanguage/ExpressionFunction/DependencyInjection/Parameter.php b/src/ExpressionLanguage/ExpressionFunction/DependencyInjection/Parameter.php
index ee5b5ea32..4da745ce3 100644
--- a/src/ExpressionLanguage/ExpressionFunction/DependencyInjection/Parameter.php
+++ b/src/ExpressionLanguage/ExpressionFunction/DependencyInjection/Parameter.php
@@ -13,8 +13,8 @@ public function __construct($name = 'parameter')
     {
         parent::__construct(
             $name,
-            fn (string $value) => "$this->gqlServices->get('container')->getParameter($value)",
-            static fn (array $arguments, $paramName) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get('container')->getParameter($paramName)
+            fn (string $value) => "$this->gqlServices->get('service_container')->getParameter($value)",
+            static fn (array $arguments, $paramName) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get('service_container')->getParameter($paramName)
         );
     }
 }
diff --git a/src/ExpressionLanguage/ExpressionFunction/DependencyInjection/Service.php b/src/ExpressionLanguage/ExpressionFunction/DependencyInjection/Service.php
index d1679d424..2f03c8a34 100644
--- a/src/ExpressionLanguage/ExpressionFunction/DependencyInjection/Service.php
+++ b/src/ExpressionLanguage/ExpressionFunction/DependencyInjection/Service.php
@@ -13,8 +13,8 @@ public function __construct($name = 'service')
     {
         parent::__construct(
             $name,
-            fn (string $serviceId) => "$this->gqlServices->get('container')->get($serviceId)",
-            static fn (array $arguments, $serviceId) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get('container')->get($serviceId)
+            fn (string $serviceId) => "$this->gqlServices->get('service_container')->get($serviceId)",
+            static fn (array $arguments, $serviceId) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get('service_container')->get($serviceId)
         );
     }
 }
diff --git a/src/ExpressionLanguage/ExpressionFunction/GraphQL/Arguments.php b/src/ExpressionLanguage/ExpressionFunction/GraphQL/Arguments.php
index 18366d36a..4edc33e9d 100644
--- a/src/ExpressionLanguage/ExpressionFunction/GraphQL/Arguments.php
+++ b/src/ExpressionLanguage/ExpressionFunction/GraphQL/Arguments.php
@@ -13,8 +13,8 @@ public function __construct()
     {
         parent::__construct(
             'arguments',
-            fn ($mapping, $data) => "$this->gqlServices->get('container')->get('overblog_graphql.arguments_transformer')->getArguments($mapping, $data, \$info)",
-            static fn (array $arguments, $mapping, $data) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get('container')->get('overblog_graphql.arguments_transformer')->getArguments($mapping, $data, $arguments['info'])
+            fn ($mapping, $data) => "$this->gqlServices->get('service_container')->get('overblog_graphql.arguments_transformer')->getArguments($mapping, $data, \$info)",
+            static fn (array $arguments, $mapping, $data) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get('service_container')->get('overblog_graphql.arguments_transformer')->getArguments($mapping, $data, $arguments['info'])
         );
     }
 }
diff --git a/src/ExpressionLanguage/ExpressionFunction/Security/GetUser.php b/src/ExpressionLanguage/ExpressionFunction/Security/GetUser.php
index 2d773a65b..951580dda 100644
--- a/src/ExpressionLanguage/ExpressionFunction/Security/GetUser.php
+++ b/src/ExpressionLanguage/ExpressionFunction/Security/GetUser.php
@@ -6,6 +6,7 @@
 
 use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction;
 use Overblog\GraphQLBundle\Generator\TypeGenerator;
+use Overblog\GraphQLBundle\Security\Security;
 
 final class GetUser extends ExpressionFunction
 {
@@ -13,8 +14,8 @@ public function __construct()
     {
         parent::__construct(
             'getUser',
-            fn () => "$this->gqlServices->get('security')->getUser()",
-            static fn (array $arguments) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get('security')->getUser()
+            fn () => "$this->gqlServices->get('".Security::class.'\')->getUser()',
+            static fn (array $arguments) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get(Security::class)->getUser()
         );
     }
 }
diff --git a/src/ExpressionLanguage/ExpressionFunction/Security/HasAnyPermission.php b/src/ExpressionLanguage/ExpressionFunction/Security/HasAnyPermission.php
index d947ec423..69b9023f2 100644
--- a/src/ExpressionLanguage/ExpressionFunction/Security/HasAnyPermission.php
+++ b/src/ExpressionLanguage/ExpressionFunction/Security/HasAnyPermission.php
@@ -6,6 +6,7 @@
 
 use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction;
 use Overblog\GraphQLBundle\Generator\TypeGenerator;
+use Overblog\GraphQLBundle\Security\Security;
 
 final class HasAnyPermission extends ExpressionFunction
 {
@@ -13,8 +14,8 @@ public function __construct()
     {
         parent::__construct(
             'hasAnyPermission',
-            fn ($object, $permissions) => "$this->gqlServices->get('security')->hasAnyPermission($object, $permissions)",
-            static fn (array $arguments, $object, $permissions) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get('security')->hasAnyPermission($object, $permissions)
+            fn ($object, $permissions) => "$this->gqlServices->get('".Security::class."')->hasAnyPermission($object, $permissions)",
+            static fn (array $arguments, $object, $permissions) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get(Security::class)->hasAnyPermission($object, $permissions)
         );
     }
 }
diff --git a/src/ExpressionLanguage/ExpressionFunction/Security/HasAnyRole.php b/src/ExpressionLanguage/ExpressionFunction/Security/HasAnyRole.php
index 79d4ada77..242a1bb9b 100644
--- a/src/ExpressionLanguage/ExpressionFunction/Security/HasAnyRole.php
+++ b/src/ExpressionLanguage/ExpressionFunction/Security/HasAnyRole.php
@@ -6,6 +6,7 @@
 
 use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction;
 use Overblog\GraphQLBundle\Generator\TypeGenerator;
+use Overblog\GraphQLBundle\Security\Security;
 
 final class HasAnyRole extends ExpressionFunction
 {
@@ -13,8 +14,8 @@ public function __construct()
     {
         parent::__construct(
             'hasAnyRole',
-            fn ($roles) => "$this->gqlServices->get('security')->hasAnyRole($roles)",
-            static fn (array $arguments, $roles) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get('security')->hasAnyRole($roles)
+            fn ($roles) => "$this->gqlServices->get('".Security::class."')->hasAnyRole($roles)",
+            static fn (array $arguments, $roles) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get(Security::class)->hasAnyRole($roles)
         );
     }
 }
diff --git a/src/ExpressionLanguage/ExpressionFunction/Security/HasPermission.php b/src/ExpressionLanguage/ExpressionFunction/Security/HasPermission.php
index 2a76085a5..d617bbe44 100644
--- a/src/ExpressionLanguage/ExpressionFunction/Security/HasPermission.php
+++ b/src/ExpressionLanguage/ExpressionFunction/Security/HasPermission.php
@@ -6,6 +6,7 @@
 
 use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction;
 use Overblog\GraphQLBundle\Generator\TypeGenerator;
+use Overblog\GraphQLBundle\Security\Security;
 
 final class HasPermission extends ExpressionFunction
 {
@@ -13,8 +14,8 @@ public function __construct()
     {
         parent::__construct(
             'hasPermission',
-            fn ($object, $permission) => "$this->gqlServices->get('security')->hasPermission($object, $permission)",
-            static fn (array $arguments, $object, $permission) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get('security')->hasPermission($object, $permission)
+            fn ($object, $permission) => "$this->gqlServices->get('".Security::class."')->hasPermission($object, $permission)",
+            static fn (array $arguments, $object, $permission) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get(Security::class)->hasPermission($object, $permission)
         );
     }
 }
diff --git a/src/ExpressionLanguage/ExpressionFunction/Security/HasRole.php b/src/ExpressionLanguage/ExpressionFunction/Security/HasRole.php
index 2bfb5aa34..14cadd75f 100644
--- a/src/ExpressionLanguage/ExpressionFunction/Security/HasRole.php
+++ b/src/ExpressionLanguage/ExpressionFunction/Security/HasRole.php
@@ -6,6 +6,7 @@
 
 use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction;
 use Overblog\GraphQLBundle\Generator\TypeGenerator;
+use Overblog\GraphQLBundle\Security\Security;
 
 final class HasRole extends ExpressionFunction
 {
@@ -13,8 +14,8 @@ public function __construct()
     {
         parent::__construct(
             'hasRole',
-            fn ($role) => "$this->gqlServices->get('security')->hasRole($role)",
-            static fn (array $arguments, $role) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get('security')->hasRole($role)
+            fn ($role) => "$this->gqlServices->get('".Security::class."')->hasRole($role)",
+            static fn (array $arguments, $role) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get(Security::class)->hasRole($role)
         );
     }
 }
diff --git a/src/ExpressionLanguage/ExpressionFunction/Security/IsAnonymous.php b/src/ExpressionLanguage/ExpressionFunction/Security/IsAnonymous.php
index 042374615..0a7620237 100644
--- a/src/ExpressionLanguage/ExpressionFunction/Security/IsAnonymous.php
+++ b/src/ExpressionLanguage/ExpressionFunction/Security/IsAnonymous.php
@@ -6,6 +6,7 @@
 
 use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction;
 use Overblog\GraphQLBundle\Generator\TypeGenerator;
+use Overblog\GraphQLBundle\Security\Security;
 
 final class IsAnonymous extends ExpressionFunction
 {
@@ -13,8 +14,8 @@ public function __construct()
     {
         parent::__construct(
             'isAnonymous',
-            fn () => "$this->gqlServices->get('security')->isAnonymous()",
-            static fn (array $arguments) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get('security')->isAnonymous()
+            fn () => "$this->gqlServices->get('".Security::class.'\')->isAnonymous()',
+            static fn (array $arguments) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get(Security::class)->isAnonymous()
         );
     }
 }
diff --git a/src/ExpressionLanguage/ExpressionFunction/Security/IsAuthenticated.php b/src/ExpressionLanguage/ExpressionFunction/Security/IsAuthenticated.php
index 78589758e..ee306b1c0 100644
--- a/src/ExpressionLanguage/ExpressionFunction/Security/IsAuthenticated.php
+++ b/src/ExpressionLanguage/ExpressionFunction/Security/IsAuthenticated.php
@@ -6,6 +6,7 @@
 
 use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction;
 use Overblog\GraphQLBundle\Generator\TypeGenerator;
+use Overblog\GraphQLBundle\Security\Security;
 
 final class IsAuthenticated extends ExpressionFunction
 {
@@ -13,8 +14,8 @@ public function __construct()
     {
         parent::__construct(
             'isAuthenticated',
-            fn () => "$this->gqlServices->get('security')->isAuthenticated()",
-            static fn (array $arguments) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get('security')->isAuthenticated()
+            fn () => "$this->gqlServices->get('".Security::class.'\')->isAuthenticated()',
+            static fn (array $arguments) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get(Security::class)->isAuthenticated()
         );
     }
 }
diff --git a/src/ExpressionLanguage/ExpressionFunction/Security/IsFullyAuthenticated.php b/src/ExpressionLanguage/ExpressionFunction/Security/IsFullyAuthenticated.php
index c6d4ce9c7..5a4af74df 100644
--- a/src/ExpressionLanguage/ExpressionFunction/Security/IsFullyAuthenticated.php
+++ b/src/ExpressionLanguage/ExpressionFunction/Security/IsFullyAuthenticated.php
@@ -6,6 +6,7 @@
 
 use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction;
 use Overblog\GraphQLBundle\Generator\TypeGenerator;
+use Overblog\GraphQLBundle\Security\Security;
 
 final class IsFullyAuthenticated extends ExpressionFunction
 {
@@ -13,8 +14,8 @@ public function __construct()
     {
         parent::__construct(
             'isFullyAuthenticated',
-            fn () => "$this->gqlServices->get('security')->isFullyAuthenticated()",
-            fn (array $arguments) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get('security')->isFullyAuthenticated()
+            fn () => "$this->gqlServices->get('".Security::class.'\')->isFullyAuthenticated()',
+            fn (array $arguments) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get(Security::class)->isFullyAuthenticated()
         );
     }
 }
diff --git a/src/ExpressionLanguage/ExpressionFunction/Security/IsGranted.php b/src/ExpressionLanguage/ExpressionFunction/Security/IsGranted.php
index ede9c6043..5c1a3e5d5 100644
--- a/src/ExpressionLanguage/ExpressionFunction/Security/IsGranted.php
+++ b/src/ExpressionLanguage/ExpressionFunction/Security/IsGranted.php
@@ -6,6 +6,7 @@
 
 use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction;
 use Overblog\GraphQLBundle\Generator\TypeGenerator;
+use Overblog\GraphQLBundle\Security\Security;
 
 final class IsGranted extends ExpressionFunction
 {
@@ -13,8 +14,8 @@ public function __construct()
     {
         parent::__construct(
             'isGranted',
-            fn ($attributes, $object = 'null') => "$this->gqlServices->get('security')->isGranted($attributes, $object)",
-            static fn (array $arguments, $attributes, $object = null) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get('security')->isGranted($attributes, $object)
+            fn ($attributes, $object = 'null') => "$this->gqlServices->get('".Security::class."')->isGranted($attributes, $object)",
+            static fn (array $arguments, $attributes, $object = null) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get(Security::class)->isGranted($attributes, $object)
         );
     }
 }
diff --git a/src/ExpressionLanguage/ExpressionFunction/Security/IsRememberMe.php b/src/ExpressionLanguage/ExpressionFunction/Security/IsRememberMe.php
index 16290da38..ae22f7526 100644
--- a/src/ExpressionLanguage/ExpressionFunction/Security/IsRememberMe.php
+++ b/src/ExpressionLanguage/ExpressionFunction/Security/IsRememberMe.php
@@ -6,6 +6,7 @@
 
 use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction;
 use Overblog\GraphQLBundle\Generator\TypeGenerator;
+use Overblog\GraphQLBundle\Security\Security;
 
 final class IsRememberMe extends ExpressionFunction
 {
@@ -13,8 +14,8 @@ public function __construct()
     {
         parent::__construct(
             'isRememberMe',
-            fn () => "$this->gqlServices->get('security')->isRememberMe()",
-            static fn (array $arguments) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get('security')->isRememberMe()
+            fn () => "$this->gqlServices->get('".Security::class.'\')->isRememberMe()',
+            static fn (array $arguments) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get(Security::class)->isRememberMe()
         );
     }
 }
diff --git a/src/ExpressionLanguage/ExpressionLanguage.php b/src/ExpressionLanguage/ExpressionLanguage.php
index d6709bce4..c7754b6e0 100644
--- a/src/ExpressionLanguage/ExpressionLanguage.php
+++ b/src/ExpressionLanguage/ExpressionLanguage.php
@@ -4,6 +4,7 @@
 
 namespace Overblog\GraphQLBundle\ExpressionLanguage;
 
+use Overblog\GraphQLBundle\Generator\TypeGenerator;
 use Symfony\Component\ExpressionLanguage\Expression;
 use Symfony\Component\ExpressionLanguage\ExpressionLanguage as BaseExpressionLanguage;
 use Symfony\Component\ExpressionLanguage\Lexer;
@@ -19,16 +20,29 @@ final class ExpressionLanguage extends BaseExpressionLanguage
     public const KNOWN_NAMES = ['value', 'args', 'context', 'info', 'object', 'validator', 'errors', 'childrenComplexity', 'typeName', 'fieldName'];
     public const EXPRESSION_TRIGGER = '@=';
 
+    /** @var array<string, string> */
     public array $globalNames = [];
 
-    public function addGlobalName(string $index, string $name): void
+    /** @var array<string, string> */
+    public array $expressionVariableServiceIds = [];
+
+    public function addExpressionVariableNameServiceId(string $expressionVarName, string $serviceId): void
+    {
+        $this->expressionVariableServiceIds[$expressionVarName] = $serviceId;
+        $this->addGlobalName(sprintf(TypeGenerator::GRAPHQL_SERVICES.'->get(\'%s\')', $serviceId), $expressionVarName);
+    }
+
+    /**
+     * @return array<string, string>
+     */
+    public function getExpressionVariableServiceIds(): array
     {
-        $this->globalNames[$index] = $name;
+        return $this->expressionVariableServiceIds;
     }
 
-    public function getGlobalNames(): array
+    public function addGlobalName(string $code, string $expressionVarName): void
     {
-        return array_values($this->globalNames);
+        $this->globalNames[$code] = $expressionVarName;
     }
 
     /**
diff --git a/src/Generator/TypeBuilder.php b/src/Generator/TypeBuilder.php
index 8434f30b2..c4fdfb404 100644
--- a/src/Generator/TypeBuilder.php
+++ b/src/Generator/TypeBuilder.php
@@ -21,6 +21,7 @@
 use Murtukov\PHPCodeGenerator\Literal;
 use Murtukov\PHPCodeGenerator\PhpFile;
 use Murtukov\PHPCodeGenerator\Utils;
+use Overblog\GraphQLBundle\Definition\ArgumentFactory;
 use Overblog\GraphQLBundle\Definition\ConfigProcessor;
 use Overblog\GraphQLBundle\Definition\GraphQLServices;
 use Overblog\GraphQLBundle\Definition\Resolver\AliasedInterface;
@@ -785,7 +786,7 @@ private function buildComplexity($complexity)
                     ->addArgument('childrenComplexity')
                     ->addArgument('arguments', '', [])
                     ->bindVar(TypeGenerator::GRAPHQL_SERVICES)
-                    ->append('$args = ', "$this->gqlServices->get('argumentFactory')->create(\$arguments)")
+                    ->append('$args = ', "$this->gqlServices->get('".ArgumentFactory::class."')->create(\$arguments)")
                     ->append('return ', $expression)
                 ;
             }
diff --git a/src/Validator/Constraints/ExpressionValidator.php b/src/Validator/Constraints/ExpressionValidator.php
index 2aff26b8f..4675c3928 100644
--- a/src/Validator/Constraints/ExpressionValidator.php
+++ b/src/Validator/Constraints/ExpressionValidator.php
@@ -66,17 +66,17 @@ public function validate($value, Constraint $constraint): void
      */
     private function addGlobalVariables($expression, array &$variables): void
     {
-        $globalVariables = $this->expressionLanguage->getGlobalNames();
+        $expressionVariableServiceIds = $this->expressionLanguage->getExpressionVariableServiceIds();
         foreach (ExpressionLanguage::extractExpressionVarNames($expression) as $extractExpressionVarName) {
+            $serviceIds = $expressionVariableServiceIds[$extractExpressionVarName] ?? null;
             if (
                 isset($variables[$extractExpressionVarName])
-                || !$this->graphQLServices->has($extractExpressionVarName)
-                || !in_array($extractExpressionVarName, $globalVariables)
+                || !$this->graphQLServices->has($serviceIds)
             ) {
                 continue;
             }
 
-            $variables[$extractExpressionVarName] = $this->graphQLServices->get($extractExpressionVarName);
+            $variables[$extractExpressionVarName] = $this->graphQLServices->get($serviceIds);
         }
     }
 }
diff --git a/tests/ExpressionLanguage/ExpressionFunction/DependencyInjection/ParameterTest.php b/tests/ExpressionLanguage/ExpressionFunction/DependencyInjection/ParameterTest.php
index 5f0e7cd1c..22d19381d 100644
--- a/tests/ExpressionLanguage/ExpressionFunction/DependencyInjection/ParameterTest.php
+++ b/tests/ExpressionLanguage/ExpressionFunction/DependencyInjection/ParameterTest.php
@@ -25,9 +25,8 @@ protected function getFunctions()
     public function testParameterCompilation($name): void
     {
         ${TypeGenerator::GRAPHQL_SERVICES} = $this->createGraphQLServices(
-            ['container' => $this->getDIContainerMock([], ['test' => 5])]
+            ['service_container' => $this->getDIContainerMock([], ['test' => 5])]
         );
-        ${TypeGenerator::GRAPHQL_SERVICES}->get('container');
         $this->assertSame(5, eval('return '.$this->expressionLanguage->compile($name.'("test")').';'));
     }
 
@@ -37,7 +36,7 @@ public function testParameterCompilation($name): void
      */
     public function testParameterEvaluation($name): void
     {
-        $services = $this->createGraphQLServices(['container' => $this->getDIContainerMock([], ['test' => 5])]);
+        $services = $this->createGraphQLServices(['service_container' => $this->getDIContainerMock([], ['test' => 5])]);
         $this->assertSame(
             5,
             $this->expressionLanguage->evaluate($name.'("test")', [TypeGenerator::GRAPHQL_SERVICES => $services])
diff --git a/tests/ExpressionLanguage/ExpressionFunction/DependencyInjection/ServiceTest.php b/tests/ExpressionLanguage/ExpressionFunction/DependencyInjection/ServiceTest.php
index f264c1dfb..110852792 100644
--- a/tests/ExpressionLanguage/ExpressionFunction/DependencyInjection/ServiceTest.php
+++ b/tests/ExpressionLanguage/ExpressionFunction/DependencyInjection/ServiceTest.php
@@ -26,8 +26,7 @@ public function testServiceCompilation(string $name): void
     {
         $object = new stdClass();
 
-        ${TypeGenerator::GRAPHQL_SERVICES} = $this->createGraphQLServices(['container' => $this->getDIContainerMock(['toto' => $object])]);
-        ${TypeGenerator::GRAPHQL_SERVICES}->get('container');
+        ${TypeGenerator::GRAPHQL_SERVICES} = $this->createGraphQLServices(['service_container' => $this->getDIContainerMock(['toto' => $object])]);
         $this->assertSame($object, eval('return '.$this->expressionLanguage->compile($name.'("toto")').';'));
     }
 
@@ -37,7 +36,7 @@ public function testServiceCompilation(string $name): void
     public function testServiceEvaluation(string $name): void
     {
         $object = new stdClass();
-        $services = $this->createGraphQLServices(['container' => $this->getDIContainerMock(['toto' => $object])]);
+        $services = $this->createGraphQLServices(['service_container' => $this->getDIContainerMock(['toto' => $object])]);
 
         $this->assertSame(
             $object,
diff --git a/tests/ExpressionLanguage/ExpressionFunction/GraphQL/ArgumentsTest.php b/tests/ExpressionLanguage/ExpressionFunction/GraphQL/ArgumentsTest.php
index cdb5647cd..571a21314 100644
--- a/tests/ExpressionLanguage/ExpressionFunction/GraphQL/ArgumentsTest.php
+++ b/tests/ExpressionLanguage/ExpressionFunction/GraphQL/ArgumentsTest.php
@@ -83,7 +83,7 @@ public function testEvaluator(): void
 
         $services = $this->createGraphQLServices(
             [
-                'container' => $this->getDIContainerMock(['overblog_graphql.arguments_transformer' => $transformer]),
+                'service_container' => $this->getDIContainerMock(['overblog_graphql.arguments_transformer' => $transformer]),
             ]
         );
 
diff --git a/tests/ExpressionLanguage/ExpressionFunction/Security/GetUserTest.php b/tests/ExpressionLanguage/ExpressionFunction/Security/GetUserTest.php
index ef21537ef..670842c8f 100644
--- a/tests/ExpressionLanguage/ExpressionFunction/Security/GetUserTest.php
+++ b/tests/ExpressionLanguage/ExpressionFunction/Security/GetUserTest.php
@@ -31,7 +31,7 @@ public function testEvaluator(): void
         }
         $coreSecurity = $this->createMock(CoreSecurity::class);
         $coreSecurity->method('getUser')->willReturn($testUser);
-        $services = $this->createGraphQLServices(['security' => new Security($coreSecurity)]);
+        $services = $this->createGraphQLServices([Security::class => new Security($coreSecurity)]);
 
         $user = $this->expressionLanguage->evaluate('getUser()', [TypeGenerator::GRAPHQL_SERVICES => $services]);
         $this->assertInstanceOf(UserInterface::class, $user);
@@ -40,9 +40,8 @@ public function testEvaluator(): void
     public function testGetUserNoTokenStorage(): void
     {
         ${TypeGenerator::GRAPHQL_SERVICES} = $this->createGraphQLServices(
-            ['security' => new Security($this->createMock(CoreSecurity::class))]
+            [Security::class => new Security($this->createMock(CoreSecurity::class))]
         );
-        ${TypeGenerator::GRAPHQL_SERVICES}->get('security');
         $this->assertNull(eval($this->getCompileCode()));
     }
 
@@ -51,14 +50,13 @@ public function testGetUserNoToken(): void
         $tokenStorage = $this->getMockBuilder(TokenStorageInterface::class)->getMock();
         ${TypeGenerator::GRAPHQL_SERVICES} = $this->createGraphQLServices(
             [
-                'security' => new Security(
+                Security::class => new Security(
                     new CoreSecurity(
                         $this->getDIContainerMock(['security.token_storage' => $tokenStorage])
                     )
                 ),
             ]
         );
-        ${TypeGenerator::GRAPHQL_SERVICES}->get('security');
 
         $this->getDIContainerMock(['security.token_storage' => $tokenStorage]);
         $this->assertNull(eval($this->getCompileCode()));
@@ -77,14 +75,13 @@ public function testGetUser($user, $expectedUser): void
 
         ${TypeGenerator::GRAPHQL_SERVICES} = $this->createGraphQLServices(
             [
-                'security' => new Security(
+                Security::class => new Security(
                     new CoreSecurity(
                         $this->getDIContainerMock(['security.token_storage' => $tokenStorage])
                     )
                 ),
             ]
         );
-        ${TypeGenerator::GRAPHQL_SERVICES}->get('security');
 
         $token
             ->expects($this->once())
diff --git a/tests/ExpressionLanguage/ExpressionFunction/Security/HasAnyPermissionTest.php b/tests/ExpressionLanguage/ExpressionFunction/Security/HasAnyPermissionTest.php
index a2f9399a8..225079e3a 100644
--- a/tests/ExpressionLanguage/ExpressionFunction/Security/HasAnyPermissionTest.php
+++ b/tests/ExpressionLanguage/ExpressionFunction/Security/HasAnyPermissionTest.php
@@ -6,6 +6,7 @@
 
 use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security\HasAnyPermission;
 use Overblog\GraphQLBundle\Generator\TypeGenerator;
+use Overblog\GraphQLBundle\Security\Security;
 use Overblog\GraphQLBundle\Tests\ExpressionLanguage\TestCase;
 use stdClass;
 
@@ -28,7 +29,7 @@ public function testEvaluator(): void
             ],
             $this->any()
         );
-        $services = $this->createGraphQLServices(['security' => $security]);
+        $services = $this->createGraphQLServices([Security::class => $security]);
 
         $hasPermission = $this->expressionLanguage->evaluate(
             $this->testedExpression,
diff --git a/tests/ExpressionLanguage/ExpressionFunction/Security/HasAnyRoleTest.php b/tests/ExpressionLanguage/ExpressionFunction/Security/HasAnyRoleTest.php
index 5d728050c..ca859b4b1 100644
--- a/tests/ExpressionLanguage/ExpressionFunction/Security/HasAnyRoleTest.php
+++ b/tests/ExpressionLanguage/ExpressionFunction/Security/HasAnyRoleTest.php
@@ -6,6 +6,7 @@
 
 use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security\HasAnyRole;
 use Overblog\GraphQLBundle\Generator\TypeGenerator;
+use Overblog\GraphQLBundle\Security\Security;
 use Overblog\GraphQLBundle\Tests\ExpressionLanguage\TestCase;
 
 final class HasAnyRoleTest extends TestCase
@@ -18,7 +19,7 @@ protected function getFunctions()
     public function testEvaluator(): void
     {
         $security = $this->getSecurityIsGrantedWithExpectation('ROLE_ADMIN', $this->any());
-        $services = $this->createGraphQLServices(['security' => $security]);
+        $services = $this->createGraphQLServices([Security::class => $security]);
 
         $hasRole = $this->expressionLanguage->evaluate(
             'hasAnyRole(["ROLE_ADMIN", "ROLE_USER"])',
diff --git a/tests/ExpressionLanguage/ExpressionFunction/Security/HasPermissionTest.php b/tests/ExpressionLanguage/ExpressionFunction/Security/HasPermissionTest.php
index a14921e6f..f5b3d2b84 100644
--- a/tests/ExpressionLanguage/ExpressionFunction/Security/HasPermissionTest.php
+++ b/tests/ExpressionLanguage/ExpressionFunction/Security/HasPermissionTest.php
@@ -6,6 +6,7 @@
 
 use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security\HasPermission;
 use Overblog\GraphQLBundle\Generator\TypeGenerator;
+use Overblog\GraphQLBundle\Security\Security;
 use Overblog\GraphQLBundle\Tests\ExpressionLanguage\TestCase;
 use stdClass;
 
@@ -28,7 +29,7 @@ public function testEvaluator(): void
             ],
             $this->any()
         );
-        $services = $this->createGraphQLServices(['security' => $security]);
+        $services = $this->createGraphQLServices([Security::class => $security]);
 
         $hasPermission = $this->expressionLanguage->evaluate(
             $this->testedExpression,
diff --git a/tests/ExpressionLanguage/ExpressionFunction/Security/HasRoleTest.php b/tests/ExpressionLanguage/ExpressionFunction/Security/HasRoleTest.php
index d1f803077..c64b8eb3b 100644
--- a/tests/ExpressionLanguage/ExpressionFunction/Security/HasRoleTest.php
+++ b/tests/ExpressionLanguage/ExpressionFunction/Security/HasRoleTest.php
@@ -6,6 +6,7 @@
 
 use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security\HasRole;
 use Overblog\GraphQLBundle\Generator\TypeGenerator;
+use Overblog\GraphQLBundle\Security\Security;
 use Overblog\GraphQLBundle\Tests\ExpressionLanguage\TestCase;
 
 final class HasRoleTest extends TestCase
@@ -21,7 +22,7 @@ public function testEvaluator(): void
             'ROLE_USER',
             $this->any()
         );
-        $services = $this->createGraphQLServices(['security' => $security]);
+        $services = $this->createGraphQLServices([Security::class => $security]);
 
         $hasRole = $this->expressionLanguage->evaluate('hasRole("ROLE_USER")', [TypeGenerator::GRAPHQL_SERVICES => $services]);
         $this->assertTrue($hasRole);
diff --git a/tests/ExpressionLanguage/ExpressionFunction/Security/IsAnonymousTest.php b/tests/ExpressionLanguage/ExpressionFunction/Security/IsAnonymousTest.php
index d1e0ae2f5..88e0dd2e9 100644
--- a/tests/ExpressionLanguage/ExpressionFunction/Security/IsAnonymousTest.php
+++ b/tests/ExpressionLanguage/ExpressionFunction/Security/IsAnonymousTest.php
@@ -6,6 +6,7 @@
 
 use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security\IsAnonymous;
 use Overblog\GraphQLBundle\Generator\TypeGenerator;
+use Overblog\GraphQLBundle\Security\Security;
 use Overblog\GraphQLBundle\Tests\ExpressionLanguage\TestCase;
 
 final class IsAnonymousTest extends TestCase
@@ -21,7 +22,7 @@ public function testEvaluator(): void
             'IS_AUTHENTICATED_ANONYMOUSLY',
             $this->any()
         );
-        $services = $this->createGraphQLServices(['security' => $security]);
+        $services = $this->createGraphQLServices([Security::class => $security]);
 
         $isAnonymous = $this->expressionLanguage->evaluate('isAnonymous()', [TypeGenerator::GRAPHQL_SERVICES => $services]);
         $this->assertTrue($isAnonymous);
diff --git a/tests/ExpressionLanguage/ExpressionFunction/Security/IsAuthenticatedTest.php b/tests/ExpressionLanguage/ExpressionFunction/Security/IsAuthenticatedTest.php
index 1a5864671..993d6bfe6 100644
--- a/tests/ExpressionLanguage/ExpressionFunction/Security/IsAuthenticatedTest.php
+++ b/tests/ExpressionLanguage/ExpressionFunction/Security/IsAuthenticatedTest.php
@@ -6,6 +6,7 @@
 
 use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security\IsAuthenticated;
 use Overblog\GraphQLBundle\Generator\TypeGenerator;
+use Overblog\GraphQLBundle\Security\Security;
 use Overblog\GraphQLBundle\Tests\ExpressionLanguage\TestCase;
 
 final class IsAuthenticatedTest extends TestCase
@@ -21,7 +22,7 @@ public function testEvaluator(): void
             $this->matchesRegularExpression('/^IS_AUTHENTICATED_(REMEMBERED|FULLY)$/'),
             $this->any()
         );
-        $gqlServices = $this->createGraphQLServices(['security' => $security]);
+        $gqlServices = $this->createGraphQLServices([Security::class => $security]);
 
         $isAuthenticated = $this->expressionLanguage->evaluate(
             'isAuthenticated()',
diff --git a/tests/ExpressionLanguage/ExpressionFunction/Security/IsFullyAuthenticatedTest.php b/tests/ExpressionLanguage/ExpressionFunction/Security/IsFullyAuthenticatedTest.php
index 70f5a099d..3f5535c2a 100644
--- a/tests/ExpressionLanguage/ExpressionFunction/Security/IsFullyAuthenticatedTest.php
+++ b/tests/ExpressionLanguage/ExpressionFunction/Security/IsFullyAuthenticatedTest.php
@@ -6,6 +6,7 @@
 
 use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security\IsFullyAuthenticated;
 use Overblog\GraphQLBundle\Generator\TypeGenerator;
+use Overblog\GraphQLBundle\Security\Security;
 use Overblog\GraphQLBundle\Tests\ExpressionLanguage\TestCase;
 
 final class IsFullyAuthenticatedTest extends TestCase
@@ -21,7 +22,7 @@ public function testEvaluator(): void
             'IS_AUTHENTICATED_FULLY',
             $this->any()
         );
-        $gqlServices = $this->createGraphQLServices(['security' => $security]);
+        $gqlServices = $this->createGraphQLServices([Security::class => $security]);
 
         $isFullyAuthenticated = $this->expressionLanguage->evaluate(
             'isFullyAuthenticated()',
diff --git a/tests/ExpressionLanguage/ExpressionFunction/Security/IsGrantedTest.php b/tests/ExpressionLanguage/ExpressionFunction/Security/IsGrantedTest.php
index 4091ba8f0..8b8761f32 100644
--- a/tests/ExpressionLanguage/ExpressionFunction/Security/IsGrantedTest.php
+++ b/tests/ExpressionLanguage/ExpressionFunction/Security/IsGrantedTest.php
@@ -6,6 +6,7 @@
 
 use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security\IsGranted;
 use Overblog\GraphQLBundle\Generator\TypeGenerator;
+use Overblog\GraphQLBundle\Security\Security;
 use Overblog\GraphQLBundle\Tests\ExpressionLanguage\TestCase;
 
 final class IsGrantedTest extends TestCase
@@ -21,7 +22,7 @@ public function testEvaluator(): void
             $this->matchesRegularExpression('/^ROLE_(USER|ADMIN)$/'),
             $this->any()
         );
-        $gqlServices = $this->createGraphQLServices(['security' => $security]);
+        $gqlServices = $this->createGraphQLServices([Security::class => $security]);
 
         $this->assertTrue(
             $this->expressionLanguage->evaluate('isGranted("ROLE_USER")', [TypeGenerator::GRAPHQL_SERVICES => $gqlServices])
diff --git a/tests/ExpressionLanguage/ExpressionFunction/Security/IsRememberMeTest.php b/tests/ExpressionLanguage/ExpressionFunction/Security/IsRememberMeTest.php
index 277ae1a8c..1eb9d695c 100644
--- a/tests/ExpressionLanguage/ExpressionFunction/Security/IsRememberMeTest.php
+++ b/tests/ExpressionLanguage/ExpressionFunction/Security/IsRememberMeTest.php
@@ -6,6 +6,7 @@
 
 use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction\Security\IsRememberMe;
 use Overblog\GraphQLBundle\Generator\TypeGenerator;
+use Overblog\GraphQLBundle\Security\Security;
 use Overblog\GraphQLBundle\Tests\ExpressionLanguage\TestCase;
 
 final class IsRememberMeTest extends TestCase
@@ -21,7 +22,7 @@ public function testEvaluator(): void
             'IS_AUTHENTICATED_REMEMBERED',
             $this->any()
         );
-        $gqlServices = $this->createGraphQLServices(['security' => $security]);
+        $gqlServices = $this->createGraphQLServices([Security::class => $security]);
 
         $isRememberMe = $this->expressionLanguage->evaluate('isRememberMe()', [TypeGenerator::GRAPHQL_SERVICES => $gqlServices]);
         $this->assertTrue($isRememberMe);
diff --git a/tests/ExpressionLanguage/TestCase.php b/tests/ExpressionLanguage/TestCase.php
index 0cd96f6f6..0bec6f948 100644
--- a/tests/ExpressionLanguage/TestCase.php
+++ b/tests/ExpressionLanguage/TestCase.php
@@ -8,6 +8,7 @@
 use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionLanguage;
 use Overblog\GraphQLBundle\Generator\TypeGenerator;
 use Overblog\GraphQLBundle\Resolver\MutationResolver;
+use Overblog\GraphQLBundle\Resolver\QueryResolver;
 use Overblog\GraphQLBundle\Resolver\TypeResolver;
 use Overblog\GraphQLBundle\Security\Security;
 use Overblog\GraphQLBundle\Tests\DIContainerMockTrait;
@@ -57,9 +58,8 @@ protected function assertExpressionCompile(
     ): void {
         $code = $this->expressionLanguage->compile($expression, array_keys($vars));
         ${TypeGenerator::GRAPHQL_SERVICES} = $this->createGraphQLServices([
-            'security' => $this->getSecurityIsGrantedWithExpectation($with, $expects, $return),
+            Security::class => $this->getSecurityIsGrantedWithExpectation($with, $expects, $return),
         ]);
-        ${TypeGenerator::GRAPHQL_SERVICES}->get('security');
         extract($vars);
 
         $this->$assertMethod(eval('return '.$code.';'));
@@ -106,9 +106,9 @@ private function getCoreSecurityMock(): CoreSecurity
     protected function createGraphQLServices(array $services = []): GraphQLServices
     {
         $locateableServices = [
-            'typeResolver' => fn () => $this->createMock(TypeResolver::class),
-            'queryResolver' => fn () => $this->createMock(TypeResolver::class),
-            'mutationResolver' => fn () => $$this->createMock(MutationResolver::class),
+            TypeResolver::class => fn () => $this->createMock(TypeResolver::class),
+            QueryResolver::class => fn () => $this->createMock(QueryResolver::class),
+            MutationResolver::class => fn () => $$this->createMock(MutationResolver::class),
         ];
 
         foreach ($services as $id => $service) {

From 387c0ac57344020d9f560ce42c8154aaf7c57d32 Mon Sep 17 00:00:00 2001
From: Jeremiah VALERIE <jeremiah@overblog.com>
Date: Sun, 16 May 2021 00:15:59 +0200
Subject: [PATCH 03/14] wip

---
 .../Compiler/GraphQLServicesPass.php          | 85 ++++++++++++++++++-
 src/Generator/TypeBuilder.php                 |  2 +-
 .../connection/mapping/connection.types.yaml  |  8 +-
 .../App/config/connection/services.yml        |  5 +-
 4 files changed, 91 insertions(+), 9 deletions(-)

diff --git a/src/DependencyInjection/Compiler/GraphQLServicesPass.php b/src/DependencyInjection/Compiler/GraphQLServicesPass.php
index 56d63d706..c91fff80c 100644
--- a/src/DependencyInjection/Compiler/GraphQLServicesPass.php
+++ b/src/DependencyInjection/Compiler/GraphQLServicesPass.php
@@ -4,10 +4,11 @@
 
 namespace Overblog\GraphQLBundle\DependencyInjection\Compiler;
 
-use InvalidArgumentException;
 use Overblog\GraphQLBundle\Definition\GraphQLServices;
 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
 use Symfony\Component\DependencyInjection\Reference;
 use function is_string;
 use function sprintf;
@@ -19,6 +20,8 @@ final class GraphQLServicesPass implements CompilerPassInterface
      */
     public function process(ContainerBuilder $container): void
     {
+        $this->tagAllNotTaggedGraphQLServices($container);
+
         $taggedServices = $container->findTaggedServiceIds('overblog_graphql.service', true);
 
         $locateableServices = [];
@@ -51,6 +54,84 @@ public function process(ContainerBuilder $container): void
             ['container', 'service_container']
         );
 
-        $container->findDefinition(GraphQLServices::class)->addArgument($locateableServices);
+        $container->findDefinition(GraphQLServices::class)->addArgument(array_unique($locateableServices));
+    }
+
+    private function tagAllNotTaggedGraphQLServices(ContainerBuilder $container): void
+    {
+        if (!$container->hasParameter('overblog_graphql_types.config')) {
+            return;
+        }
+        /** @var array $configs */
+        $configs = $container->getParameter('overblog_graphql_types.config');
+        foreach ($configs as &$typeConfig) {
+            switch ($typeConfig['type']) {
+                case 'object':
+                    if (isset($typeConfig['config']['fieldResolver'])) {
+                        $this->resolveServiceIdAndMethod($container, $typeConfig['config']['fieldResolver']);
+                    }
+
+                    foreach ($typeConfig['config']['fields'] as &$field) {
+                        if (isset($field['resolver'])) {
+                            $this->resolveServiceIdAndMethod($container, $field['resolver']);
+                        }
+                    }
+                    break;
+
+                case 'interface':
+                case 'union':
+                    if (isset($typeConfig['config']['typeResolver'])) {
+                        $this->resolveServiceIdAndMethod($container, $typeConfig['config']['typeResolver']);
+                    }
+                break;
+            }
+        }
+        $container->setParameter('overblog_graphql_types.config', $configs);
+    }
+
+    private function resolveServiceIdAndMethod(ContainerBuilder $container, ?array &$resolver): void
+    {
+        if (!isset($resolver['id']) && !isset($resolver['method'])) {
+            return;
+        }
+        $originalId = $resolver['id'] ?? null;
+        $originalMethod = $resolver['method'] ?? null;
+
+        if (null === $originalId) {
+            [$id, $method] = explode('::', $originalMethod, 2) + [null, null];
+            $throw = false;
+        } else {
+            $id = $originalId;
+            $method = $originalMethod;
+            $throw = true;
+        }
+
+        try {
+            $definition = $container->getDefinition($id);
+        } catch (ServiceNotFoundException $e) {
+            // get Alias real service ID
+            try {
+                $alias = $container->getAlias($id);
+                $id = (string) $alias;
+                $definition = $container->getDefinition($id);
+            } catch (ServiceNotFoundException | InvalidArgumentException $e) {
+                if ($throw) {
+                    throw $e;
+                }
+                $resolver['id'] = null;
+                $resolver['method'] = $originalMethod;
+
+                return;
+            }
+        }
+        if (
+            !$definition->hasTag('overblog_graphql.service')
+            && !$definition->hasTag('overblog_graphql.global_variable')
+        ) {
+            $definition->addTag('overblog_graphql.service');
+        }
+
+        $resolver['id'] = $id;
+        $resolver['method'] = $method;
     }
 }
diff --git a/src/Generator/TypeBuilder.php b/src/Generator/TypeBuilder.php
index c4fdfb404..7b683988d 100644
--- a/src/Generator/TypeBuilder.php
+++ b/src/Generator/TypeBuilder.php
@@ -886,7 +886,7 @@ protected function buildCallback(Callback $callback, array $argNames, ?callable
                 return $expressionBuilder($callback->expression);
             }
         } elseif (null !== $callback->id) {
-            $fn = "$this->gqlServices->get('callbacks')->get('$callback->id')";
+            $fn = "$this->gqlServices->get('$callback->id')";
             if ($callback->method) {
                 return Collection::numeric([$fn, $callback->method]);
             } else {
diff --git a/tests/Functional/App/config/connection/mapping/connection.types.yaml b/tests/Functional/App/config/connection/mapping/connection.types.yaml
index ebb1b1db8..b45b9b4ce 100644
--- a/tests/Functional/App/config/connection/mapping/connection.types.yaml
+++ b/tests/Functional/App/config/connection/mapping/connection.types.yaml
@@ -4,7 +4,7 @@ Query:
         fields:
             user:
                 type: User
-                resolve: '@=query("query")'
+                resolver: 'Overblog\GraphQLBundle\Tests\Functional\App\Resolver\ConnectionResolver::resolveQuery'
 
 User:
     type: object
@@ -16,15 +16,15 @@ User:
             friends:
                 type: friendConnection
                 argsBuilder: "Relay::Connection"
-                resolve: '@=query("friends", value, args)'
+                resolver: 'overblog_graphql.test.resolver.node::friendsResolver'
             friendsForward:
                 type: userConnection
                 argsBuilder: "Relay::ForwardConnection"
-                resolve: '@=query("friends", value, args)'
+                resolver: 'overblog_graphql.test.resolver.node::friendsResolver'
             friendsBackward:
                 type: userConnection
                 argsBuilder: "Relay::BackwardConnection"
-                resolve: '@=query("friends", value, args)'
+                resolver: 'overblog_graphql.test.resolver.node::friendsResolver'
 
 friendConnection:
     type: relay-connection
diff --git a/tests/Functional/App/config/connection/services.yml b/tests/Functional/App/config/connection/services.yml
index e0498c639..62fc74216 100644
--- a/tests/Functional/App/config/connection/services.yml
+++ b/tests/Functional/App/config/connection/services.yml
@@ -1,6 +1,5 @@
 services:
-    overblog_graphql.test.resolver.node:
-        class: Overblog\GraphQLBundle\Tests\Functional\App\Resolver\ConnectionResolver
+    Overblog\GraphQLBundle\Tests\Functional\App\Resolver\ConnectionResolver:
         arguments:
             - "@overblog_graphql.promise_adapter"
         tags:
@@ -9,3 +8,5 @@ services:
             - { name: "overblog_graphql.query", alias: "query", method: "resolveQuery" }
             - { name: "overblog_graphql.query", alias: "connection", method: "resolveConnection" }
             - { name: "overblog_graphql.query", alias: "promise", method: "resolvePromiseFullFilled" }
+
+    overblog_graphql.test.resolver.node: '@Overblog\GraphQLBundle\Tests\Functional\App\Resolver\ConnectionResolver'

From 7cbbcd30cfe3d2b40e2c5110acd76d27ff828b9a Mon Sep 17 00:00:00 2001
From: Jeremiah VALERIE <jeremiah@overblog.com>
Date: Mon, 17 May 2021 22:19:19 +0200
Subject: [PATCH 04/14] wip

---
 src/Config/InterfaceTypeDefinition.php        |  4 +-
 src/Config/ObjectTypeDefinition.php           |  4 +-
 src/Config/TypeDefinition.php                 |  4 +-
 src/Config/TypeWithOutputFieldsDefinition.php |  4 +-
 src/Config/UnionTypeDefinition.php            |  4 +-
 .../Compiler/GraphQLServicesPass.php          | 81 ----------------
 .../IdentifyCallbackServiceIdsPass.php        | 94 +++++++++++++++++++
 src/OverblogGraphQLBundle.php                 |  2 +
 src/Relay/Node/NodeDefinition.php             |  5 +-
 .../connection/mapping/connection.types.yaml  |  9 +-
 10 files changed, 114 insertions(+), 97 deletions(-)
 create mode 100644 src/DependencyInjection/Compiler/IdentifyCallbackServiceIdsPass.php

diff --git a/src/Config/InterfaceTypeDefinition.php b/src/Config/InterfaceTypeDefinition.php
index 42d5133f6..fb2e5b83c 100644
--- a/src/Config/InterfaceTypeDefinition.php
+++ b/src/Config/InterfaceTypeDefinition.php
@@ -12,14 +12,14 @@ public function getDefinition(): ArrayNodeDefinition
     {
         /** @var ArrayNodeDefinition $node */
         $node = self::createNode('_interface_config');
-        $this->resolverNormalization($node, 'typeResolver', 'resolveType');
+        $this->callbackNormalization($node, 'typeResolver', 'resolveType');
 
         /** @phpstan-ignore-next-line */
         $node
             ->children()
                 ->append($this->nameSection())
                 ->append($this->outputFieldsSection())
-                ->append($this->resolverSection('typeResolver', 'GraphQL type resolver'))
+                ->append($this->callbackSection('typeResolver', 'GraphQL type resolver'))
                 ->append($this->descriptionSection())
                 ->arrayNode('interfaces')
                     ->prototype('scalar')->info('One of internal or custom interface types.')->end()
diff --git a/src/Config/ObjectTypeDefinition.php b/src/Config/ObjectTypeDefinition.php
index 41d06e469..8b83fcd5e 100644
--- a/src/Config/ObjectTypeDefinition.php
+++ b/src/Config/ObjectTypeDefinition.php
@@ -16,7 +16,7 @@ public function getDefinition(): ArrayNodeDefinition
 
         /** @var ArrayNodeDefinition $node */
         $node = $builder->getRootNode();
-        $this->resolverNormalization($node, 'fieldResolver', 'resolveField');
+        $this->callbackNormalization($node, 'fieldResolver', 'resolveField');
 
         /** @phpstan-ignore-next-line */
         $node
@@ -30,7 +30,7 @@ public function getDefinition(): ArrayNodeDefinition
                     ->prototype('scalar')->info('One of internal or custom interface types.')->end()
                 ->end()
                 ->variableNode('isTypeOf')->end()
-                ->append($this->resolverSection('fieldResolver', 'GraphQL field value resolver'))
+                ->append($this->callbackSection('fieldResolver', 'GraphQL field value resolver'))
                 ->variableNode('fieldsDefaultAccess')
                     ->info('Default access control to fields (expression language can be use here)')
                 ->end()
diff --git a/src/Config/TypeDefinition.php b/src/Config/TypeDefinition.php
index 86df0c0b5..549ff46a2 100644
--- a/src/Config/TypeDefinition.php
+++ b/src/Config/TypeDefinition.php
@@ -153,7 +153,7 @@ protected function typeSection(bool $isRequired = false): ScalarNodeDefinition
         return $node;
     }
 
-    protected function resolverNormalization(NodeDefinition $node, string $new, string $old): void
+    protected function callbackNormalization(NodeDefinition $node, string $new, string $old): void
     {
         $node
             ->beforeNormalization()
@@ -195,7 +195,7 @@ protected function resolverNormalization(NodeDefinition $node, string $new, stri
             ;
     }
 
-    protected function resolverSection(string $name, string $info): ArrayNodeDefinition
+    protected function callbackSection(string $name, string $info): ArrayNodeDefinition
     {
         /** @var ArrayNodeDefinition $node */
         $node = self::createNode($name);
diff --git a/src/Config/TypeWithOutputFieldsDefinition.php b/src/Config/TypeWithOutputFieldsDefinition.php
index bb7ae544d..d9dcd523e 100644
--- a/src/Config/TypeWithOutputFieldsDefinition.php
+++ b/src/Config/TypeWithOutputFieldsDefinition.php
@@ -18,7 +18,7 @@ protected function outputFieldsSection(): NodeDefinition
         $node->isRequired()->requiresAtLeastOneElement();
 
         $prototype = $node->useAttributeAsKey('name', false)->prototype('array');
-        $this->resolverNormalization($prototype, 'resolver', 'resolve');
+        $this->callbackNormalization($prototype, 'resolver', 'resolve');
 
         /** @phpstan-ignore-next-line */
         $prototype
@@ -69,7 +69,7 @@ protected function outputFieldsSection(): NodeDefinition
                         ->end()
                     ->end()
                 ->end()
-                ->append($this->resolverSection('resolver', 'GraphQL value resolver'))
+                ->append($this->callbackSection('resolver', 'GraphQL value resolver'))
                 ->append($this->descriptionSection())
                 ->append($this->deprecationReasonSection())
                 ->variableNode('access')
diff --git a/src/Config/UnionTypeDefinition.php b/src/Config/UnionTypeDefinition.php
index fa48a922e..cde466f16 100644
--- a/src/Config/UnionTypeDefinition.php
+++ b/src/Config/UnionTypeDefinition.php
@@ -12,7 +12,7 @@ public function getDefinition(): ArrayNodeDefinition
     {
         /** @var ArrayNodeDefinition $node */
         $node = self::createNode('_union_config');
-        $this->resolverNormalization($node, 'typeResolver', 'resolveType');
+        $this->callbackNormalization($node, 'typeResolver', 'resolveType');
 
         /** @phpstan-ignore-next-line */
         $node
@@ -25,7 +25,7 @@ public function getDefinition(): ArrayNodeDefinition
                     ->isRequired()
                     ->requiresAtLeastOneElement()
                 ->end()
-                ->append($this->resolverSection('typeResolver', 'GraphQL type resolver'))
+                ->append($this->callbackSection('typeResolver', 'GraphQL type resolver'))
                 ->append($this->descriptionSection())
             ->end();
 
diff --git a/src/DependencyInjection/Compiler/GraphQLServicesPass.php b/src/DependencyInjection/Compiler/GraphQLServicesPass.php
index c91fff80c..bfa6a3067 100644
--- a/src/DependencyInjection/Compiler/GraphQLServicesPass.php
+++ b/src/DependencyInjection/Compiler/GraphQLServicesPass.php
@@ -8,7 +8,6 @@
 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
-use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
 use Symfony\Component\DependencyInjection\Reference;
 use function is_string;
 use function sprintf;
@@ -20,8 +19,6 @@ final class GraphQLServicesPass implements CompilerPassInterface
      */
     public function process(ContainerBuilder $container): void
     {
-        $this->tagAllNotTaggedGraphQLServices($container);
-
         $taggedServices = $container->findTaggedServiceIds('overblog_graphql.service', true);
 
         $locateableServices = [];
@@ -56,82 +53,4 @@ public function process(ContainerBuilder $container): void
 
         $container->findDefinition(GraphQLServices::class)->addArgument(array_unique($locateableServices));
     }
-
-    private function tagAllNotTaggedGraphQLServices(ContainerBuilder $container): void
-    {
-        if (!$container->hasParameter('overblog_graphql_types.config')) {
-            return;
-        }
-        /** @var array $configs */
-        $configs = $container->getParameter('overblog_graphql_types.config');
-        foreach ($configs as &$typeConfig) {
-            switch ($typeConfig['type']) {
-                case 'object':
-                    if (isset($typeConfig['config']['fieldResolver'])) {
-                        $this->resolveServiceIdAndMethod($container, $typeConfig['config']['fieldResolver']);
-                    }
-
-                    foreach ($typeConfig['config']['fields'] as &$field) {
-                        if (isset($field['resolver'])) {
-                            $this->resolveServiceIdAndMethod($container, $field['resolver']);
-                        }
-                    }
-                    break;
-
-                case 'interface':
-                case 'union':
-                    if (isset($typeConfig['config']['typeResolver'])) {
-                        $this->resolveServiceIdAndMethod($container, $typeConfig['config']['typeResolver']);
-                    }
-                break;
-            }
-        }
-        $container->setParameter('overblog_graphql_types.config', $configs);
-    }
-
-    private function resolveServiceIdAndMethod(ContainerBuilder $container, ?array &$resolver): void
-    {
-        if (!isset($resolver['id']) && !isset($resolver['method'])) {
-            return;
-        }
-        $originalId = $resolver['id'] ?? null;
-        $originalMethod = $resolver['method'] ?? null;
-
-        if (null === $originalId) {
-            [$id, $method] = explode('::', $originalMethod, 2) + [null, null];
-            $throw = false;
-        } else {
-            $id = $originalId;
-            $method = $originalMethod;
-            $throw = true;
-        }
-
-        try {
-            $definition = $container->getDefinition($id);
-        } catch (ServiceNotFoundException $e) {
-            // get Alias real service ID
-            try {
-                $alias = $container->getAlias($id);
-                $id = (string) $alias;
-                $definition = $container->getDefinition($id);
-            } catch (ServiceNotFoundException | InvalidArgumentException $e) {
-                if ($throw) {
-                    throw $e;
-                }
-                $resolver['id'] = null;
-                $resolver['method'] = $originalMethod;
-
-                return;
-            }
-        }
-        if (
-            !$definition->hasTag('overblog_graphql.service')
-            && !$definition->hasTag('overblog_graphql.global_variable')
-        ) {
-            $definition->addTag('overblog_graphql.service');
-        }
-
-        $resolver['id'] = $id;
-        $resolver['method'] = $method;
-    }
 }
diff --git a/src/DependencyInjection/Compiler/IdentifyCallbackServiceIdsPass.php b/src/DependencyInjection/Compiler/IdentifyCallbackServiceIdsPass.php
new file mode 100644
index 000000000..dfa04066d
--- /dev/null
+++ b/src/DependencyInjection/Compiler/IdentifyCallbackServiceIdsPass.php
@@ -0,0 +1,94 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Overblog\GraphQLBundle\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
+
+final class IdentifyCallbackServiceIdsPass implements CompilerPassInterface
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function process(ContainerBuilder $container): void
+    {
+        if (!$container->hasParameter('overblog_graphql_types.config')) {
+            return;
+        }
+        /** @var array $configs */
+        $configs = $container->getParameter('overblog_graphql_types.config');
+        foreach ($configs as &$typeConfig) {
+            switch ($typeConfig['type']) {
+                case 'object':
+                    if (isset($typeConfig['config']['fieldResolver'])) {
+                        $this->resolveServiceIdAndMethod($container, $typeConfig['config']['fieldResolver']);
+                    }
+
+                    foreach ($typeConfig['config']['fields'] as &$field) {
+                        if (isset($field['resolver'])) {
+                            $this->resolveServiceIdAndMethod($container, $field['resolver']);
+                        }
+                    }
+                    break;
+
+                case 'interface':
+                case 'union':
+                    if (isset($typeConfig['config']['typeResolver'])) {
+                        $this->resolveServiceIdAndMethod($container, $typeConfig['config']['typeResolver']);
+                    }
+                    break;
+            }
+        }
+        $container->setParameter('overblog_graphql_types.config', $configs);
+    }
+
+    private function resolveServiceIdAndMethod(ContainerBuilder $container, ?array &$callback): void
+    {
+        if (!isset($callback['id']) && !isset($callback['method'])) {
+            return;
+        }
+        $originalId = $callback['id'] ?? null;
+        $originalMethod = $callback['method'] ?? null;
+
+        if (null === $originalId) {
+            [$id, $method] = explode('::', $originalMethod, 2) + [null, null];
+            $throw = false;
+        } else {
+            $id = $originalId;
+            $method = $originalMethod;
+            $throw = true;
+        }
+
+        try {
+            $definition = $container->getDefinition($id);
+        } catch (ServiceNotFoundException $e) {
+            // get Alias real service ID
+            try {
+                $alias = $container->getAlias($id);
+                $id = (string) $alias;
+                $definition = $container->getDefinition($id);
+            } catch (ServiceNotFoundException | InvalidArgumentException $e) {
+                if ($throw) {
+                    throw $e;
+                }
+                $callback['id'] = null;
+                $callback['method'] = $originalMethod;
+
+                return;
+            }
+        }
+        if (
+            !$definition->hasTag('overblog_graphql.service')
+            && !$definition->hasTag('overblog_graphql.global_variable')
+        ) {
+            $definition->addTag('overblog_graphql.service');
+        }
+
+        $callback['id'] = $id;
+        $callback['method'] = $method;
+    }
+}
diff --git a/src/OverblogGraphQLBundle.php b/src/OverblogGraphQLBundle.php
index 8c406eeb7..2b79876c5 100644
--- a/src/OverblogGraphQLBundle.php
+++ b/src/OverblogGraphQLBundle.php
@@ -8,6 +8,7 @@
 use Overblog\GraphQLBundle\DependencyInjection\Compiler\ConfigParserPass;
 use Overblog\GraphQLBundle\DependencyInjection\Compiler\ExpressionFunctionPass;
 use Overblog\GraphQLBundle\DependencyInjection\Compiler\GraphQLServicesPass;
+use Overblog\GraphQLBundle\DependencyInjection\Compiler\IdentifyCallbackServiceIdsPass;
 use Overblog\GraphQLBundle\DependencyInjection\Compiler\MutationTaggedServiceMappingTaggedPass;
 use Overblog\GraphQLBundle\DependencyInjection\Compiler\QueryTaggedServiceMappingPass;
 use Overblog\GraphQLBundle\DependencyInjection\Compiler\ResolverMapTaggedServiceMappingPass;
@@ -38,6 +39,7 @@ public function build(ContainerBuilder $container): void
 
         //TypeGeneratorPass must be before TypeTaggedServiceMappingPass
         $container->addCompilerPass(new ConfigParserPass());
+        $container->addCompilerPass(new IdentifyCallbackServiceIdsPass());
         $container->addCompilerPass(new GraphQLServicesPass());
         $container->addCompilerPass(new ExpressionFunctionPass());
         $container->addCompilerPass(new ResolverMethodAliasesPass());
diff --git a/src/Relay/Node/NodeDefinition.php b/src/Relay/Node/NodeDefinition.php
index b7affe13f..1719d4a9d 100644
--- a/src/Relay/Node/NodeDefinition.php
+++ b/src/Relay/Node/NodeDefinition.php
@@ -11,13 +11,12 @@ final class NodeDefinition implements MappingInterface
     public function toMappingDefinition(array $config): array
     {
         $name = $config['name'];
-        $resolveType = empty($config['resolveType']) ? null : $config['resolveType'];
 
         return [
             $name => [
                 'type' => 'interface',
                 'config' => [
-                    'name' => $config['name'],
+                    'name' => $name,
                     'description' => 'Fetches an object given its ID',
                     'fields' => [
                         'id' => [
@@ -25,7 +24,7 @@ public function toMappingDefinition(array $config): array
                             'description' => 'The ID of an object',
                         ],
                     ],
-                    'resolveType' => $resolveType,
+                    'typeResolver' => $config['typeResolver'] ?? $config['resolveType'] ?? null,
                 ],
             ],
         ];
diff --git a/tests/Functional/App/config/connection/mapping/connection.types.yaml b/tests/Functional/App/config/connection/mapping/connection.types.yaml
index b45b9b4ce..7e2d4fea2 100644
--- a/tests/Functional/App/config/connection/mapping/connection.types.yaml
+++ b/tests/Functional/App/config/connection/mapping/connection.types.yaml
@@ -16,11 +16,14 @@ User:
             friends:
                 type: friendConnection
                 argsBuilder: "Relay::Connection"
-                resolver: 'overblog_graphql.test.resolver.node::friendsResolver'
+                resolver:
+                    method: 'overblog_graphql.test.resolver.node::friendsResolver'
             friendsForward:
                 type: userConnection
                 argsBuilder: "Relay::ForwardConnection"
-                resolver: 'overblog_graphql.test.resolver.node::friendsResolver'
+                resolver:
+                    id: 'overblog_graphql.test.resolver.node'
+                    method: 'friendsResolver'
             friendsBackward:
                 type: userConnection
                 argsBuilder: "Relay::BackwardConnection"
@@ -38,7 +41,7 @@ friendConnection:
         connectionFields:
             totalCount:
                 type: Int
-                resolve: '@=query("connection")'
+                resolver: '@=query("connection")'
 
 userConnection:
     type: relay-connection

From ed76483b1f1654ba497452b119ffc339131ed0c7 Mon Sep 17 00:00:00 2001
From: Jeremiah VALERIE <mcg-web@users.noreply.github.com>
Date: Thu, 27 May 2021 09:12:54 +0200
Subject: [PATCH 05/14] Fix typo

Co-authored-by: Timur Murtukov <murtukov@gmail.com>
---
 src/Config/TypeDefinition.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Config/TypeDefinition.php b/src/Config/TypeDefinition.php
index 549ff46a2..38e4e0219 100644
--- a/src/Config/TypeDefinition.php
+++ b/src/Config/TypeDefinition.php
@@ -187,7 +187,7 @@ protected function callbackNormalization(NodeDefinition $node, string $new, stri
             ->validate()
                 ->ifTrue(fn (array $v) => !empty($v[$new]) && !empty($v[$old]))
                 ->thenInvalid(sprintf(
-                    '"%s" and "%s" should not be use together in "%%s".',
+                    '"%s" and "%s" should not be used together in "%%s".',
                     $new,
                     $old,
                 ))

From dfe4d29ad654db3455ad8764e5ba8659247ea5eb Mon Sep 17 00:00:00 2001
From: Jeremiah VALERIE <mcg-web@users.noreply.github.com>
Date: Thu, 27 May 2021 09:15:10 +0200
Subject: [PATCH 06/14] Improve syntax

Co-authored-by: Timur Murtukov <murtukov@gmail.com>
---
 .../ExpressionFunction/Security/IsRememberMe.php                | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/ExpressionLanguage/ExpressionFunction/Security/IsRememberMe.php b/src/ExpressionLanguage/ExpressionFunction/Security/IsRememberMe.php
index ae22f7526..03afb8300 100644
--- a/src/ExpressionLanguage/ExpressionFunction/Security/IsRememberMe.php
+++ b/src/ExpressionLanguage/ExpressionFunction/Security/IsRememberMe.php
@@ -14,7 +14,7 @@ public function __construct()
     {
         parent::__construct(
             'isRememberMe',
-            fn () => "$this->gqlServices->get('".Security::class.'\')->isRememberMe()',
+            fn () => "$this->gqlServices->get('".Security::class."')->isRememberMe()",
             static fn (array $arguments) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get(Security::class)->isRememberMe()
         );
     }

From 55370a300b62184224a6d8a7cc78df6ed660c5c9 Mon Sep 17 00:00:00 2001
From: Jeremiah VALERIE <mcg-web@users.noreply.github.com>
Date: Thu, 27 May 2021 09:15:27 +0200
Subject: [PATCH 07/14] Improve syntax

Co-authored-by: Timur Murtukov <murtukov@gmail.com>
---
 .../ExpressionFunction/Security/IsAnonymous.php                 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/ExpressionLanguage/ExpressionFunction/Security/IsAnonymous.php b/src/ExpressionLanguage/ExpressionFunction/Security/IsAnonymous.php
index 0a7620237..26333605b 100644
--- a/src/ExpressionLanguage/ExpressionFunction/Security/IsAnonymous.php
+++ b/src/ExpressionLanguage/ExpressionFunction/Security/IsAnonymous.php
@@ -14,7 +14,7 @@ public function __construct()
     {
         parent::__construct(
             'isAnonymous',
-            fn () => "$this->gqlServices->get('".Security::class.'\')->isAnonymous()',
+            fn () => "$this->gqlServices->get('".Security::class."')->isAnonymous()",
             static fn (array $arguments) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get(Security::class)->isAnonymous()
         );
     }

From dfffaf6be4f90d0ec2954595c2ed760b70806272 Mon Sep 17 00:00:00 2001
From: Jeremiah VALERIE <mcg-web@users.noreply.github.com>
Date: Thu, 27 May 2021 09:15:35 +0200
Subject: [PATCH 08/14] Improve syntax

Co-authored-by: Timur Murtukov <murtukov@gmail.com>
---
 src/ExpressionLanguage/ExpressionFunction/Security/GetUser.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/ExpressionLanguage/ExpressionFunction/Security/GetUser.php b/src/ExpressionLanguage/ExpressionFunction/Security/GetUser.php
index 951580dda..9c53bc3f3 100644
--- a/src/ExpressionLanguage/ExpressionFunction/Security/GetUser.php
+++ b/src/ExpressionLanguage/ExpressionFunction/Security/GetUser.php
@@ -14,7 +14,7 @@ public function __construct()
     {
         parent::__construct(
             'getUser',
-            fn () => "$this->gqlServices->get('".Security::class.'\')->getUser()',
+            fn () => "$this->gqlServices->get('".Security::class."')->getUser()",
             static fn (array $arguments) => $arguments[TypeGenerator::GRAPHQL_SERVICES]->get(Security::class)->getUser()
         );
     }

From 2f969dd570ba44b38c6b860fa4a0bed9de70ec82 Mon Sep 17 00:00:00 2001
From: Jeremiah VALERIE <jeremiah@overblog.com>
Date: Wed, 22 Dec 2021 11:15:44 +0100
Subject: [PATCH 09/14] Fix after rebase

---
 .../Compiler/IdentifyCallbackServiceIdsPass.php          | 2 +-
 src/Generator/TypeBuilder.php                            | 9 +++++----
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/src/DependencyInjection/Compiler/IdentifyCallbackServiceIdsPass.php b/src/DependencyInjection/Compiler/IdentifyCallbackServiceIdsPass.php
index dfa04066d..45c4fe431 100644
--- a/src/DependencyInjection/Compiler/IdentifyCallbackServiceIdsPass.php
+++ b/src/DependencyInjection/Compiler/IdentifyCallbackServiceIdsPass.php
@@ -71,7 +71,7 @@ private function resolveServiceIdAndMethod(ContainerBuilder $container, ?array &
                 $alias = $container->getAlias($id);
                 $id = (string) $alias;
                 $definition = $container->getDefinition($id);
-            } catch (ServiceNotFoundException | InvalidArgumentException $e) {
+            } catch (ServiceNotFoundException|InvalidArgumentException $e) {
                 if ($throw) {
                     throw $e;
                 }
diff --git a/src/Generator/TypeBuilder.php b/src/Generator/TypeBuilder.php
index 7b683988d..523a51928 100644
--- a/src/Generator/TypeBuilder.php
+++ b/src/Generator/TypeBuilder.php
@@ -31,6 +31,7 @@
 use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionLanguage as EL;
 use Overblog\GraphQLBundle\Generator\Config\Arg;
 use Overblog\GraphQLBundle\Generator\Config\Callback;
+use Overblog\GraphQLBundle\Generator\Config\Config as GeneratorConfig;
 use Overblog\GraphQLBundle\Generator\Config\Field;
 use Overblog\GraphQLBundle\Generator\Config\Validation;
 use Overblog\GraphQLBundle\Generator\Converter\ExpressionConverter;
@@ -79,7 +80,7 @@ final class TypeBuilder
     private ExpressionConverter $expressionConverter;
     private PhpFile $file;
     private string $namespace;
-    private Config\Config $config;
+    private GeneratorConfig $config;
     private string $type;
     private string $currentField;
     private string $gqlServices = '$'.TypeGenerator::GRAPHQL_SERVICES;
@@ -114,7 +115,7 @@ public function __construct(ExpressionConverter $expressionConverter, string $na
     public function build(array $config, string $type): PhpFile
     {
         // This values should be accessible from every method
-        $this->config = new Config\Config($config);
+        $this->config = new GeneratorConfig($config);
         $this->type = $type;
 
         $this->file = PhpFile::new()->setNamespace($this->namespace);
@@ -439,7 +440,7 @@ private function buildScalarCallback($callback, string $fieldName)
      *
      * @throws GeneratorException
      */
-    private function buildResolver(Callback $resolver, ?array $groups = null): ?GeneratorInterface
+    private function buildResolver(Callback $resolver, ?array $groups = null): GeneratorInterface
     {
         // TODO: before creating an input validator, check if any validation rules are defined
         return $this->buildCallback(
@@ -874,7 +875,7 @@ private function buildTypeResolver(Callback $typeResolver): GeneratorInterface
         return $this->buildCallback($typeResolver, ['value', 'context', 'info']);
     }
 
-    protected function buildCallback(Callback $callback, array $argNames, ?callable $expressionBuilder = null): GeneratorInterface
+    private function buildCallback(Callback $callback, array $argNames, ?callable $expressionBuilder = null): GeneratorInterface
     {
         if (null !== $callback->expression) {
             if (null === $expressionBuilder) {

From 19c9ac707aef4dd04fac84b78035f97af4099424 Mon Sep 17 00:00:00 2001
From: Jeremiah VALERIE <jeremiah@overblog.com>
Date: Fri, 24 Dec 2021 21:22:24 +0100
Subject: [PATCH 10/14] Fix validator tests

---
 src/Generator/TypeBuilder.php                 |  7 +++----
 .../Validator/InputValidatorTest.php          | 20 +++++++++----------
 2 files changed, 13 insertions(+), 14 deletions(-)

diff --git a/src/Generator/TypeBuilder.php b/src/Generator/TypeBuilder.php
index 523a51928..18b1983c3 100644
--- a/src/Generator/TypeBuilder.php
+++ b/src/Generator/TypeBuilder.php
@@ -49,7 +49,6 @@
 use function reset;
 use function rtrim;
 use function strtolower;
-use function substr;
 
 /**
  * Service that exposes a single method `build` called for each GraphQL
@@ -531,7 +530,7 @@ private function buildValidationRules(Validation $validationConfig): GeneratorIn
         $array = Collection::assoc();
 
         if (null !== $validationConfig->link) {
-            if (str_contains($validationConfig->link, '::')) {
+            if (!str_contains($validationConfig->link, '::')) {
                 // e.g. App\Entity\Droid
                 $array->addItem('link', $validationConfig->link);
             } else {
@@ -911,9 +910,9 @@ private function normalizeLink(string $link): array
     {
         [$fqcn, $classMember] = explode('::', $link);
 
-        if ('$' === $classMember[0]) {
+        if (str_starts_with($classMember, '$')) {
             return [$fqcn, ltrim($classMember, '$'), 'property'];
-        } elseif (')' === substr($classMember, -1)) {
+        } elseif (str_ends_with($classMember, ')')) {
             return [$fqcn, rtrim($classMember, '()'), 'getter'];
         } else {
             return [$fqcn, $classMember, 'member'];
diff --git a/tests/Functional/Validator/InputValidatorTest.php b/tests/Functional/Validator/InputValidatorTest.php
index 4af2d1ca1..58cbcd742 100644
--- a/tests/Functional/Validator/InputValidatorTest.php
+++ b/tests/Functional/Validator/InputValidatorTest.php
@@ -30,7 +30,7 @@ public function testNoValidation(): void
 
         $result = $this->executeGraphQLRequest($query);
 
-        $this->assertTrue(empty($result['errors']));
+        $this->assertArrayNotHasKey('errors', $result);
         $this->assertTrue($result['data']['noValidation']);
     }
 
@@ -47,7 +47,7 @@ public function testSimpleValidationPasses(): void
 
         $result = $this->executeGraphQLRequest($query);
 
-        $this->assertTrue(empty($result['errors']));
+        $this->assertArrayNotHasKey('errors', $result);
         $this->assertTrue($result['data']['simpleValidation']);
     }
 
@@ -82,7 +82,7 @@ public function testLinkedConstraintsValidationPasses(): void
 
         $result = $this->executeGraphQLRequest($query);
 
-        $this->assertTrue(empty($result['errors']));
+        $this->assertArrayNotHasKey('errors', $result);
         $this->assertTrue($result['data']['linkedConstraintsValidation']);
     }
 
@@ -128,7 +128,7 @@ public function testCollectionValidationPasses(): void
 
         $result = $this->executeGraphQLRequest($query);
 
-        $this->assertTrue(empty($result['errors']));
+        $this->assertArrayNotHasKey('errors', $result);
         $this->assertTrue($result['data']['collectionValidation']);
     }
 
@@ -189,7 +189,7 @@ public function testCascadeValidationWithGroupsPasses(): void
 
         $result = $this->executeGraphQLRequest($query);
 
-        $this->assertTrue(empty($result['errors']));
+        $this->assertArrayNotHasKey('errors', $result);
         $this->assertTrue($result['data']['cascadeValidationWithGroups']);
     }
 
@@ -249,7 +249,7 @@ public function testExpressionVariablesAccessible(): void
 
         $result = $this->executeGraphQLRequest($query);
 
-        $this->assertTrue(empty($result['errors']));
+        $this->assertArrayNotHasKey('errors', $result);
         $this->assertTrue($result['data']['expressionVariablesValidation']);
     }
 
@@ -266,7 +266,7 @@ public function testAutoValidationAutoThrowPasses(): void
 
         $result = $this->executeGraphQLRequest($query);
 
-        $this->assertTrue(empty($result['errors']));
+        $this->assertArrayNotHasKey('errors', $result);
         $this->assertTrue($result['data']['autoValidationAutoThrow']);
     }
 
@@ -296,7 +296,7 @@ public function testAutoValidationNoThrowNoErrors(): void
         $query = 'mutation { autoValidationNoThrow(username: "Andrew") }';
         $result = $this->executeGraphQLRequest($query);
 
-        $this->assertTrue(empty($result['errors']));
+        $this->assertArrayNotHasKey('errors', $result);
         $this->assertTrue(false === $result['data']['autoValidationNoThrow']);
     }
 
@@ -309,7 +309,7 @@ public function testAutoValidationNoThrowHasErrors(): void
         $query = 'mutation { autoValidationNoThrow(username: "Tim") }';
         $result = $this->executeGraphQLRequest($query);
 
-        $this->assertTrue(empty($result['errors']));
+        $this->assertArrayNotHasKey('errors', $result);
         $this->assertTrue(true === $result['data']['autoValidationNoThrow']);
     }
 
@@ -341,7 +341,7 @@ public function testAutoValidationAutoThrowWithGroupsPasses(): void
 
         $result = $this->executeGraphQLRequest($query);
 
-        $this->assertTrue(empty($result['errors']));
+        $this->assertArrayNotHasKey('errors', $result);
         $this->assertTrue($result['data']['autoValidationAutoThrowWithGroups']);
     }
 

From 2c5097723b8264ca06f99a80d91ae1dc2fae8387 Mon Sep 17 00:00:00 2001
From: Jeremiah VALERIE <jeremiah@overblog.com>
Date: Fri, 24 Dec 2021 21:42:22 +0100
Subject: [PATCH 11/14] Fix code coverage

---
 src/Config/TypeDefinition.php | 9 ---------
 src/Generator/TypeBuilder.php | 7 ++-----
 2 files changed, 2 insertions(+), 14 deletions(-)

diff --git a/src/Config/TypeDefinition.php b/src/Config/TypeDefinition.php
index 38e4e0219..17632f7c8 100644
--- a/src/Config/TypeDefinition.php
+++ b/src/Config/TypeDefinition.php
@@ -215,15 +215,6 @@ protected function callbackSection(string $name, string $info): ArrayNodeDefinit
                 ->ifTrue(fn ($options) => is_string($options) && !ExpressionLanguage::stringHasTrigger($options))
                 ->then(fn ($options) => ['method' => $options])
             ->end()
-            ->beforeNormalization()
-                // clean expression
-                ->ifTrue(fn ($options) => isset($options['expression']) && is_string($options['expression']) && ExpressionLanguage::stringHasTrigger($options['expression']))
-                ->then(function ($options) {
-                    $options['expression'] = ExpressionLanguage::unprefixExpression($options['expression']);
-
-                    return $options;
-                })
-            ->end()
             ->children()
                 ->scalarNode('method')->end()
                 ->scalarNode('expression')->end()
diff --git a/src/Generator/TypeBuilder.php b/src/Generator/TypeBuilder.php
index 18b1983c3..243e7517b 100644
--- a/src/Generator/TypeBuilder.php
+++ b/src/Generator/TypeBuilder.php
@@ -887,11 +887,8 @@ private function buildCallback(Callback $callback, array $argNames, ?callable $e
             }
         } elseif (null !== $callback->id) {
             $fn = "$this->gqlServices->get('$callback->id')";
-            if ($callback->method) {
-                return Collection::numeric([$fn, $callback->method]);
-            } else {
-                return Literal::new($fn);
-            }
+
+            return Collection::numeric([$fn, $callback->method ?? '__invoke']);
         } else {
             return Literal::new("'$callback->method'");
         }

From 70103a2e03d46c96c0e9a1129a29db1055b73109 Mon Sep 17 00:00:00 2001
From: Jeremiah VALERIE <jeremiah@overblog.com>
Date: Wed, 29 Dec 2021 22:35:04 +0100
Subject: [PATCH 12/14] Add Lazy behavior when using resolver as a service

---
 src/Generator/TypeBuilder.php | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/src/Generator/TypeBuilder.php b/src/Generator/TypeBuilder.php
index 243e7517b..5f73542ff 100644
--- a/src/Generator/TypeBuilder.php
+++ b/src/Generator/TypeBuilder.php
@@ -886,9 +886,14 @@ private function buildCallback(Callback $callback, array $argNames, ?callable $e
                 return $expressionBuilder($callback->expression);
             }
         } elseif (null !== $callback->id) {
-            $fn = "$this->gqlServices->get('$callback->id')";
+            $fnExpression = "$this->gqlServices->get('$callback->id')";
+            if (null !== $callback->method) {
+                $fnExpression = "[$fnExpression, '$callback->method']";
+            }
 
-            return Collection::numeric([$fn, $callback->method ?? '__invoke']);
+            return ArrowFunction::new()
+                ->addArguments(...$argNames)
+                ->setExpression(Literal::new("($fnExpression)(...\\func_get_args())"));
         } else {
             return Literal::new("'$callback->method'");
         }

From 5e82520d473aea7955eb8495942a95f2ae9d5878 Mon Sep 17 00:00:00 2001
From: Jeremiah VALERIE <jeremiah@overblog.com>
Date: Sun, 2 Jan 2022 22:43:26 +0100
Subject: [PATCH 13/14] wip

---
 src/Config/TypeDefinition.php                 | 13 +++---
 .../IdentifyCallbackServiceIdsPass.php        | 23 ++--------
 src/Generator/Config/Callback.php             |  3 +-
 src/Generator/TypeBuilder.php                 | 37 +++++++++++----
 .../connection/mapping/connection.types.yaml  |  6 +--
 .../Controller/GraphControllerTest.php        | 46 ++++++++-----------
 6 files changed, 61 insertions(+), 67 deletions(-)

diff --git a/src/Config/TypeDefinition.php b/src/Config/TypeDefinition.php
index 17632f7c8..4ec23ffa3 100644
--- a/src/Config/TypeDefinition.php
+++ b/src/Config/TypeDefinition.php
@@ -161,9 +161,9 @@ protected function callbackNormalization(NodeDefinition $node, string $new, stri
                 ->then(function ($options) use ($old, $new) {
                     if (is_callable($options[$old])) {
                         if (is_array($options[$old])) {
-                            $options[$new]['method'] = implode('::', $options[$old]);
+                            $options[$new]['function'] = implode('::', $options[$old]);
                         } else {
-                            $options[$new]['method'] = $options[$old];
+                            $options[$new]['function'] = $options[$old];
                         }
                     } elseif (is_string($options[$old])) {
                         $options[$new]['expression'] = ExpressionLanguage::stringHasTrigger($options[$old]) ?
@@ -203,8 +203,8 @@ protected function callbackSection(string $name, string $info): ArrayNodeDefinit
         $node
             ->info($info)
             ->validate()
-                ->ifTrue(fn (array $v) => !empty($v['method']) && !empty($v['expression']))
-                ->thenInvalid('"method" and "expression" should not be use together.')
+                ->ifTrue(fn (array $v) => !empty($v['function']) && !empty($v['expression']))
+                ->thenInvalid('"function" and "expression" should not be use together.')
             ->end()
             ->beforeNormalization()
                 // Allow short syntax
@@ -213,12 +213,11 @@ protected function callbackSection(string $name, string $info): ArrayNodeDefinit
             ->end()
             ->beforeNormalization()
                 ->ifTrue(fn ($options) => is_string($options) && !ExpressionLanguage::stringHasTrigger($options))
-                ->then(fn ($options) => ['method' => $options])
+                ->then(fn ($options) => ['function' => $options])
             ->end()
             ->children()
-                ->scalarNode('method')->end()
+                ->scalarNode('function')->end()
                 ->scalarNode('expression')->end()
-                ->scalarNode('id')->end()
             ->end()
         ;
 
diff --git a/src/DependencyInjection/Compiler/IdentifyCallbackServiceIdsPass.php b/src/DependencyInjection/Compiler/IdentifyCallbackServiceIdsPass.php
index 45c4fe431..6330872ee 100644
--- a/src/DependencyInjection/Compiler/IdentifyCallbackServiceIdsPass.php
+++ b/src/DependencyInjection/Compiler/IdentifyCallbackServiceIdsPass.php
@@ -48,20 +48,10 @@ public function process(ContainerBuilder $container): void
 
     private function resolveServiceIdAndMethod(ContainerBuilder $container, ?array &$callback): void
     {
-        if (!isset($callback['id']) && !isset($callback['method'])) {
+        if (!isset($callback['function'])) {
             return;
         }
-        $originalId = $callback['id'] ?? null;
-        $originalMethod = $callback['method'] ?? null;
-
-        if (null === $originalId) {
-            [$id, $method] = explode('::', $originalMethod, 2) + [null, null];
-            $throw = false;
-        } else {
-            $id = $originalId;
-            $method = $originalMethod;
-            $throw = true;
-        }
+        [$id, $method] = explode('::', $callback['function'], 2) + [null, null];
 
         try {
             $definition = $container->getDefinition($id);
@@ -72,12 +62,6 @@ private function resolveServiceIdAndMethod(ContainerBuilder $container, ?array &
                 $id = (string) $alias;
                 $definition = $container->getDefinition($id);
             } catch (ServiceNotFoundException|InvalidArgumentException $e) {
-                if ($throw) {
-                    throw $e;
-                }
-                $callback['id'] = null;
-                $callback['method'] = $originalMethod;
-
                 return;
             }
         }
@@ -88,7 +72,6 @@ private function resolveServiceIdAndMethod(ContainerBuilder $container, ?array &
             $definition->addTag('overblog_graphql.service');
         }
 
-        $callback['id'] = $id;
-        $callback['method'] = $method;
+        $callback['function'] = "$id::$method";
     }
 }
diff --git a/src/Generator/Config/Callback.php b/src/Generator/Config/Callback.php
index a032b8224..e813d637b 100644
--- a/src/Generator/Config/Callback.php
+++ b/src/Generator/Config/Callback.php
@@ -6,7 +6,6 @@
 
 final class Callback extends AbstractConfig
 {
-    public ?string $method = null;
+    public ?string $function = null;
     public ?string $expression = null;
-    public ?string $id = null;
 }
diff --git a/src/Generator/TypeBuilder.php b/src/Generator/TypeBuilder.php
index 5f73542ff..eaab2e435 100644
--- a/src/Generator/TypeBuilder.php
+++ b/src/Generator/TypeBuilder.php
@@ -17,6 +17,7 @@
 use Murtukov\PHPCodeGenerator\Config;
 use Murtukov\PHPCodeGenerator\ConverterInterface;
 use Murtukov\PHPCodeGenerator\GeneratorInterface;
+use Murtukov\PHPCodeGenerator\IfElse;
 use Murtukov\PHPCodeGenerator\Instance;
 use Murtukov\PHPCodeGenerator\Literal;
 use Murtukov\PHPCodeGenerator\PhpFile;
@@ -885,17 +886,37 @@ private function buildCallback(Callback $callback, array $argNames, ?callable $e
             } else {
                 return $expressionBuilder($callback->expression);
             }
-        } elseif (null !== $callback->id) {
-            $fnExpression = "$this->gqlServices->get('$callback->id')";
-            if (null !== $callback->method) {
-                $fnExpression = "[$fnExpression, '$callback->method']";
+        } else {
+            if (str_contains($callback->function, '::')) {
+                $function = explode('::', $callback->function, 2);
+                $isArray = true;
+            } else {
+                $function = $callback->function;
+                $isArray = false;
             }
 
-            return ArrowFunction::new()
+            $resolverExpression = IfElse::new("$this->gqlServices->has('".($isArray ? $function[0] : $function)."')");
+            if ($isArray) {
+                $resolverExpression
+                    ->append('$resolver = ', "[$this->gqlServices->get('$function[0]'), '$function[1]']")
+                    ->createElse()
+                        ->append('$resolver = ', "'$callback->function'")
+                    ->end()
+                ;
+            } else {
+                $resolverExpression
+                    ->append('$resolver = ', "$this->gqlServices->get('$function')")
+                    ->createElse()
+                        ->append('$resolver = ', "'$function'")
+                    ->end()
+                ;
+            }
+
+            return Closure::new()
                 ->addArguments(...$argNames)
-                ->setExpression(Literal::new("($fnExpression)(...\\func_get_args())"));
-        } else {
-            return Literal::new("'$callback->method'");
+                ->bindVar(TypeGenerator::GRAPHQL_SERVICES)
+                ->append($resolverExpression)
+                ->append('return $resolver(...\\func_get_args())');
         }
     }
 
diff --git a/tests/Functional/App/config/connection/mapping/connection.types.yaml b/tests/Functional/App/config/connection/mapping/connection.types.yaml
index 7e2d4fea2..a9fc05ea8 100644
--- a/tests/Functional/App/config/connection/mapping/connection.types.yaml
+++ b/tests/Functional/App/config/connection/mapping/connection.types.yaml
@@ -17,13 +17,11 @@ User:
                 type: friendConnection
                 argsBuilder: "Relay::Connection"
                 resolver:
-                    method: 'overblog_graphql.test.resolver.node::friendsResolver'
+                    function: 'overblog_graphql.test.resolver.node::friendsResolver'
             friendsForward:
                 type: userConnection
                 argsBuilder: "Relay::ForwardConnection"
-                resolver:
-                    id: 'overblog_graphql.test.resolver.node'
-                    method: 'friendsResolver'
+                resolver: 'overblog_graphql.test.resolver.node::friendsResolver'
             friendsBackward:
                 type: userConnection
                 argsBuilder: "Relay::BackwardConnection"
diff --git a/tests/Functional/Controller/GraphControllerTest.php b/tests/Functional/Controller/GraphControllerTest.php
index 5c06d6d6a..117d379bf 100644
--- a/tests/Functional/Controller/GraphControllerTest.php
+++ b/tests/Functional/Controller/GraphControllerTest.php
@@ -66,7 +66,7 @@ final class GraphControllerTest extends TestCase
      */
     public function testEndpointAction(string $uri): void
     {
-        $client = static::createClient(['test_case' => 'connectionWithCORS']);
+        $client = self::createClient(['test_case' => 'connectionWithCORS']);
         $this->disableCatchExceptions($client);
 
         $client->request('GET', $uri, ['query' => $this->friendsQuery], [], ['CONTENT_TYPE' => 'application/graphql;charset=utf8', 'HTTP_Origin' => 'http://example.com']);
@@ -87,7 +87,7 @@ public function testEndpointWithEmptyQuery(): void
     {
         $this->expectException(BadRequestHttpException::class);
         $this->expectExceptionMessage('Must provide query parameter');
-        $client = static::createClient();
+        $client = self::createClient();
         $this->disableCatchExceptions($client);
         $client->request('GET', '/', []);
         $client->getResponse()->getContent();
@@ -97,14 +97,14 @@ public function testEndpointWithEmptyPostJsonBodyQuery(): void
     {
         $this->expectException(BadRequestHttpException::class);
         $this->expectExceptionMessage('The request content body must not be empty when using json content type request.');
-        $client = static::createClient();
+        $client = self::createClient();
         $this->disableCatchExceptions($client);
         $client->request('POST', '/', [], [], ['CONTENT_TYPE' => 'application/json']);
     }
 
     public function testEndpointWithJsonContentTypeAndGetQuery(): void
     {
-        $client = static::createClient(['test_case' => 'connectionWithCORS']);
+        $client = self::createClient(['test_case' => 'connectionWithCORS']);
         $this->disableCatchExceptions($client);
         $client->request('GET', '/', ['query' => $this->friendsQuery], [], ['CONTENT_TYPE' => 'application/json']);
         $result = $client->getResponse()->getContent();
@@ -115,7 +115,7 @@ public function testEndpointWithInvalidBodyQuery(): void
     {
         $this->expectException(BadRequestHttpException::class);
         $this->expectExceptionMessage('POST body sent invalid JSON');
-        $client = static::createClient();
+        $client = self::createClient();
         $this->disableCatchExceptions($client);
         $client->request('GET', '/', [], [], ['CONTENT_TYPE' => 'application/json'], '{');
         $client->getResponse()->getContent();
@@ -123,7 +123,7 @@ public function testEndpointWithInvalidBodyQuery(): void
 
     public function testEndpointActionWithVariables(): void
     {
-        $client = static::createClient(['test_case' => 'connection']);
+        $client = self::createClient(['test_case' => 'connection']);
         $this->disableCatchExceptions($client);
 
         $query = <<<'EOF'
@@ -151,7 +151,7 @@ public function testEndpointActionWithInvalidVariables(): void
     {
         $this->expectException(BadRequestHttpException::class);
         $this->expectExceptionMessage('Variables are invalid JSON');
-        $client = static::createClient(['test_case' => 'connection']);
+        $client = self::createClient(['test_case' => 'connection']);
         $this->disableCatchExceptions($client);
 
         $query = <<<'EOF'
@@ -167,7 +167,7 @@ public function testMultipleEndpointActionWithUnknownSchemaName(): void
     {
         $this->expectException(NotFoundHttpException::class);
         $this->expectExceptionMessage('Could not find "fake" schema.');
-        $client = static::createClient(['test_case' => 'connection']);
+        $client = self::createClient(['test_case' => 'connection']);
         $this->disableCatchExceptions($client);
 
         $query = <<<'EOF'
@@ -181,7 +181,7 @@ public function testMultipleEndpointActionWithUnknownSchemaName(): void
 
     public function testEndpointActionWithOperationName(): void
     {
-        $client = static::createClient(['test_case' => 'connection']);
+        $client = self::createClient(['test_case' => 'connection']);
         $this->disableCatchExceptions($client);
 
         $query = $this->friendsQuery."\n".$this->friendsTotalCountQuery;
@@ -196,7 +196,7 @@ public function testEndpointActionWithOperationName(): void
      */
     public function testBatchEndpointAction(string $uri): void
     {
-        $client = static::createClient(['test_case' => 'connection']);
+        $client = self::createClient(['test_case' => 'connection']);
         $this->disableCatchExceptions($client);
 
         $data = [
@@ -233,7 +233,7 @@ public function testBatchEndpointWithEmptyQuery(): void
     {
         $this->expectException(BadRequestHttpException::class);
         $this->expectExceptionMessage('Must provide at least one valid query.');
-        $client = static::createClient();
+        $client = self::createClient();
         $this->disableCatchExceptions($client);
         $client->request('GET', '/batch', [], [], ['CONTENT_TYPE' => 'application/json'], '{}');
         $client->getResponse()->getContent();
@@ -243,7 +243,7 @@ public function testBatchEndpointWrongContentType(): void
     {
         $this->expectException(BadRequestHttpException::class);
         $this->expectExceptionMessage('Batching parser only accepts "application/json" or "multipart/form-data" content-type but got "".');
-        $client = static::createClient();
+        $client = self::createClient();
         $this->disableCatchExceptions($client);
         $client->request('GET', '/batch');
         $client->getResponse()->getContent();
@@ -253,7 +253,7 @@ public function testBatchEndpointWithInvalidJson(): void
     {
         $this->expectException(BadRequestHttpException::class);
         $this->expectExceptionMessage('POST body sent invalid JSON');
-        $client = static::createClient();
+        $client = self::createClient();
         $this->disableCatchExceptions($client);
         $client->request('GET', '/batch', [], [], ['CONTENT_TYPE' => 'application/json'], '{');
         $client->getResponse()->getContent();
@@ -263,7 +263,7 @@ public function testBatchEndpointWithInvalidQuery(): void
     {
         $this->expectException(BadRequestHttpException::class);
         $this->expectExceptionMessage('1 is not a valid query');
-        $client = static::createClient();
+        $client = self::createClient();
         $this->disableCatchExceptions($client);
         $client->request('GET', '/batch', [], [], ['CONTENT_TYPE' => 'application/json'], '{"test" : {"query": 1}}');
         $client->getResponse()->getContent();
@@ -271,7 +271,7 @@ public function testBatchEndpointWithInvalidQuery(): void
 
     public function testPreflightedRequestWhenDisabled(): void
     {
-        $client = static::createClient(['test_case' => 'connection']);
+        $client = self::createClient(['test_case' => 'connection']);
         $this->disableCatchExceptions($client);
         $client->request('OPTIONS', '/', [], [], ['HTTP_Origin' => 'http://example.com']);
         $response = $client->getResponse();
@@ -281,7 +281,7 @@ public function testPreflightedRequestWhenDisabled(): void
 
     public function testUnAuthorizedMethod(): void
     {
-        $client = static::createClient(['test_case' => 'connection']);
+        $client = self::createClient(['test_case' => 'connection']);
         $this->disableCatchExceptions($client);
         $client->request('PUT', '/', [], [], ['HTTP_Origin' => 'http://example.com']);
         $this->assertSame(405, $client->getResponse()->getStatusCode());
@@ -289,7 +289,7 @@ public function testUnAuthorizedMethod(): void
 
     public function testPreflightedRequestWhenEnabled(): void
     {
-        $client = static::createClient(['test_case' => 'connectionWithCORS']);
+        $client = self::createClient(['test_case' => 'connectionWithCORS']);
         $this->disableCatchExceptions($client);
         $client->request('OPTIONS', '/batch', [], [], ['HTTP_Origin' => 'http://example.com']);
         $this->assertCORSHeadersExists($client);
@@ -297,7 +297,7 @@ public function testPreflightedRequestWhenEnabled(): void
 
     public function testNoCORSHeadersIfOriginHeaderNotExists(): void
     {
-        $client = static::createClient(['test_case' => 'connectionWithCORS']);
+        $client = self::createClient(['test_case' => 'connectionWithCORS']);
         $this->disableCatchExceptions($client);
         $client->request('GET', '/', ['query' => $this->friendsQuery], [], ['CONTENT_TYPE' => 'application/graphql']);
         $result = $client->getResponse()->getContent();
@@ -305,10 +305,7 @@ public function testNoCORSHeadersIfOriginHeaderNotExists(): void
         $this->assertCORSHeadersNotExists($client);
     }
 
-    /**
-     * @param KernelBrowser $client
-     */
-    private function assertCORSHeadersNotExists($client): void
+    private function assertCORSHeadersNotExists(KernelBrowser $client): void
     {
         $headers = $client->getResponse()->headers->all();
         $this->assertArrayNotHasKey('access-control-allow-origin', $headers);
@@ -318,10 +315,7 @@ private function assertCORSHeadersNotExists($client): void
         $this->assertArrayNotHasKey('access-control-max-age', $headers);
     }
 
-    /**
-     * @param KernelBrowser $client
-     */
-    private function assertCORSHeadersExists($client): void
+    private function assertCORSHeadersExists(KernelBrowser $client): void
     {
         $response = $client->getResponse();
         $this->assertSame(200, $response->getStatusCode());

From 1a9b1678d7f7dfe67fe39e85335e1ea41e70e6c5 Mon Sep 17 00:00:00 2001
From: Jeremiah VALERIE <jeremiah@overblog.com>
Date: Wed, 5 Jan 2022 16:17:48 +0100
Subject: [PATCH 14/14] Fix

---
 .../Compiler/IdentifyCallbackServiceIdsPass.php                | 3 +++
 .../App/config/connection/mapping/connection.types.yaml        | 2 +-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/DependencyInjection/Compiler/IdentifyCallbackServiceIdsPass.php b/src/DependencyInjection/Compiler/IdentifyCallbackServiceIdsPass.php
index 6330872ee..e7645ab0b 100644
--- a/src/DependencyInjection/Compiler/IdentifyCallbackServiceIdsPass.php
+++ b/src/DependencyInjection/Compiler/IdentifyCallbackServiceIdsPass.php
@@ -52,6 +52,9 @@ private function resolveServiceIdAndMethod(ContainerBuilder $container, ?array &
             return;
         }
         [$id, $method] = explode('::', $callback['function'], 2) + [null, null];
+        if (str_starts_with($id, '\\')) {
+            $id = ltrim($id, '\\');
+        }
 
         try {
             $definition = $container->getDefinition($id);
diff --git a/tests/Functional/App/config/connection/mapping/connection.types.yaml b/tests/Functional/App/config/connection/mapping/connection.types.yaml
index a9fc05ea8..565e083d0 100644
--- a/tests/Functional/App/config/connection/mapping/connection.types.yaml
+++ b/tests/Functional/App/config/connection/mapping/connection.types.yaml
@@ -25,7 +25,7 @@ User:
             friendsBackward:
                 type: userConnection
                 argsBuilder: "Relay::BackwardConnection"
-                resolver: 'overblog_graphql.test.resolver.node::friendsResolver'
+                resolver: '\Overblog\GraphQLBundle\Tests\Functional\App\Resolver\ConnectionResolver::friendsResolver'
 
 friendConnection:
     type: relay-connection