From c20f7913ad6f5fb98ae3f21f970d3668f751cd93 Mon Sep 17 00:00:00 2001
From: Vladyslav Lyshenko <vladyslav.lyshenko@whitebit.com>
Date: Sat, 11 Mar 2023 18:41:00 +0200
Subject: [PATCH 1/7] Added IsPublic support to InputObject

---
 src/Config/InputObjectTypeDefinition.php            | 8 ++++++++
 src/Config/Parser/MetadataParser/MetadataParser.php | 9 +++++++++
 2 files changed, 17 insertions(+)

diff --git a/src/Config/InputObjectTypeDefinition.php b/src/Config/InputObjectTypeDefinition.php
index 63cb1e471..482efc2c5 100644
--- a/src/Config/InputObjectTypeDefinition.php
+++ b/src/Config/InputObjectTypeDefinition.php
@@ -5,6 +5,7 @@
 namespace Overblog\GraphQLBundle\Config;
 
 use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
+use Symfony\Component\Config\Definition\Builder\VariableNodeDefinition;
 
 use function is_string;
 
@@ -31,6 +32,7 @@ public function getDefinition(): ArrayNodeDefinition
                         ->append($this->typeSection(true))
                         ->append($this->descriptionSection())
                         ->append($this->defaultValueSection())
+                        ->append($this->publicSection())
                         ->append($this->validationSection(self::VALIDATION_LEVEL_PROPERTY))
                     ->end()
                     ->isRequired()
@@ -41,4 +43,10 @@ public function getDefinition(): ArrayNodeDefinition
 
         return $node;
     }
+
+    protected function publicSection(): VariableNodeDefinition
+    {
+        return self::createNode('public', 'variable')
+            ->info('Visibility control to field (expression language can be used here)');
+    }
 }
diff --git a/src/Config/Parser/MetadataParser/MetadataParser.php b/src/Config/Parser/MetadataParser/MetadataParser.php
index 471c42e73..7e314ab3f 100644
--- a/src/Config/Parser/MetadataParser/MetadataParser.php
+++ b/src/Config/Parser/MetadataParser/MetadataParser.php
@@ -643,9 +643,14 @@ private static function getGraphQLInputFieldsFromMetadatas(ReflectionClass $refl
 
             /** @var Metadata\Field|null $fieldMetadata */
             $fieldMetadata = self::getFirstMetadataMatching($metadatas, Metadata\Field::class);
+            $publicMetadata = self::getFirstMetadataMatching($metadatas, Metadata\IsPublic::class);
 
             // No field metadata found
             if (null === $fieldMetadata) {
+                if (null !== $publicMetadata) {
+                    throw new InvalidArgumentException(sprintf('The metadatas %s defined on "%s" are only usable in addition of metadata %s', self::formatMetadata('Visible'), $reflector->getName(), self::formatMetadata('Field')));
+                }
+
                 continue;
             }
 
@@ -676,6 +681,10 @@ private static function getGraphQLInputFieldsFromMetadatas(ReflectionClass $refl
                 $fieldConfiguration['type'] = $fieldType;
             }
 
+            if ($publicMetadata) {
+                $fieldConfiguration['public'] = self::formatExpression($publicMetadata->value);
+            }
+
             $fieldConfiguration = array_merge(self::getDescriptionConfiguration($metadatas, true), $fieldConfiguration);
             $fields[$fieldName] = $fieldConfiguration;
         }

From 7f996c72968d9f1f1083d9b9e87f0ff2d0974fbb Mon Sep 17 00:00:00 2001
From: Vladyslav Lyshenko <vladyslav.lyshenko@entuasol.com>
Date: Mon, 27 Nov 2023 15:32:31 +0200
Subject: [PATCH 2/7] Added getCurrentSchemaName for TypeResolver

---
 src/Resolver/TypeResolver.php | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/Resolver/TypeResolver.php b/src/Resolver/TypeResolver.php
index 26b9810a8..c1769922a 100644
--- a/src/Resolver/TypeResolver.php
+++ b/src/Resolver/TypeResolver.php
@@ -28,6 +28,11 @@ public function setCurrentSchemaName(?string $currentSchemaName): void
         $this->currentSchemaName = $currentSchemaName;
     }
 
+    public function getCurrentSchemaName(): ?string
+    {
+        return $this->currentSchemaName;
+    }
+
     public function setIgnoreUnresolvableException(bool $ignoreUnresolvableException): void
     {
         $this->ignoreUnresolvableException = $ignoreUnresolvableException;

From b40b79f50d52a005082543d52acf0439d8485949 Mon Sep 17 00:00:00 2001
From: Vladyslav Lyshenko <vladyslav.lyshenko@entuasol.com>
Date: Sun, 4 Feb 2024 13:18:19 +0200
Subject: [PATCH 3/7] feat: resettable schemas

---
 src/Definition/Builder/SchemaBuilder.php      |  9 +++--
 src/Definition/Type/ExtensibleSchema.php      | 16 ++++++++
 src/DependencyInjection/Configuration.php     |  1 +
 .../OverblogGraphQLExtension.php              |  1 +
 src/Request/Executor.php                      | 38 ++++++++++++++-----
 5 files changed, 52 insertions(+), 13 deletions(-)

diff --git a/src/Definition/Builder/SchemaBuilder.php b/src/Definition/Builder/SchemaBuilder.php
index aee461512..ec62b6a7a 100644
--- a/src/Definition/Builder/SchemaBuilder.php
+++ b/src/Definition/Builder/SchemaBuilder.php
@@ -23,12 +23,12 @@ public function __construct(TypeResolver $typeResolver, bool $enableValidation =
         $this->enableValidation = $enableValidation;
     }
 
-    public function getBuilder(string $name, ?string $queryAlias, string $mutationAlias = null, string $subscriptionAlias = null, array $types = []): Closure
+    public function getBuilder(string $name, ?string $queryAlias, string $mutationAlias = null, string $subscriptionAlias = null, array $types = [], bool $resettable = false): Closure
     {
-        return function () use ($name, $queryAlias, $mutationAlias, $subscriptionAlias, $types): ExtensibleSchema {
+        return function () use ($name, $queryAlias, $mutationAlias, $subscriptionAlias, $types, $resettable): ExtensibleSchema {
             static $schema = null;
             if (null === $schema) {
-                $schema = $this->create($name, $queryAlias, $mutationAlias, $subscriptionAlias, $types);
+                $schema = $this->create($name, $queryAlias, $mutationAlias, $subscriptionAlias, $types, $resettable);
             }
 
             return $schema;
@@ -38,7 +38,7 @@ public function getBuilder(string $name, ?string $queryAlias, string $mutationAl
     /**
      * @param string[] $types
      */
-    public function create(string $name, ?string $queryAlias, string $mutationAlias = null, string $subscriptionAlias = null, array $types = []): ExtensibleSchema
+    public function create(string $name, ?string $queryAlias, string $mutationAlias = null, string $subscriptionAlias = null, array $types = [], bool $resettable = false): ExtensibleSchema
     {
         $this->typeResolver->setCurrentSchemaName($name);
         $query = $this->typeResolver->resolve($queryAlias);
@@ -46,6 +46,7 @@ public function create(string $name, ?string $queryAlias, string $mutationAlias
         $subscription = $this->typeResolver->resolve($subscriptionAlias);
 
         $schema = new ExtensibleSchema($this->buildSchemaArguments($name, $query, $mutation, $subscription, $types));
+        $schema->setIsResettable($resettable);
         $extensions = [];
 
         if ($this->enableValidation) {
diff --git a/src/Definition/Type/ExtensibleSchema.php b/src/Definition/Type/ExtensibleSchema.php
index 79b84ce7b..cbab2fcbb 100644
--- a/src/Definition/Type/ExtensibleSchema.php
+++ b/src/Definition/Type/ExtensibleSchema.php
@@ -10,6 +10,12 @@
 
 class ExtensibleSchema extends Schema
 {
+    /**
+     * Need to reset when container reset called
+     * @var bool
+     */
+    private bool $isResettable = false;
+
     public function __construct($config)
     {
         parent::__construct(
@@ -51,4 +57,14 @@ public function processExtensions()
 
         return $this;
     }
+
+    public function isResettable(): bool
+    {
+        return $this->isResettable;
+    }
+
+    public function setIsResettable(bool $isResettable): void
+    {
+        $this->isResettable = $isResettable;
+    }
 }
diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php
index a8c4c707e..5ea7c527c 100644
--- a/src/DependencyInjection/Configuration.php
+++ b/src/DependencyInjection/Configuration.php
@@ -218,6 +218,7 @@ private function definitionsSchemaSection(): ArrayNodeDefinition
                     ->scalarNode('query')->defaultNull()->end()
                     ->scalarNode('mutation')->defaultNull()->end()
                     ->scalarNode('subscription')->defaultNull()->end()
+                    ->scalarNode('resettable')->defaultFalse()->end()
                     ->arrayNode('types')
                         ->defaultValue([])
                         ->prototype('scalar')->end()
diff --git a/src/DependencyInjection/OverblogGraphQLExtension.php b/src/DependencyInjection/OverblogGraphQLExtension.php
index 257952eef..c48343a3f 100644
--- a/src/DependencyInjection/OverblogGraphQLExtension.php
+++ b/src/DependencyInjection/OverblogGraphQLExtension.php
@@ -245,6 +245,7 @@ private function setSchemaArguments(array $config, ContainerBuilder $container):
                 $schemaConfig['mutation'],
                 $schemaConfig['subscription'],
                 $schemaConfig['types'],
+                $schemaConfig['resettable'],
             ]);
             // schema
             $schemaID = sprintf('%s.schema_%s', $this->getAlias(), $schemaName);
diff --git a/src/Request/Executor.php b/src/Request/Executor.php
index 706cd4fa1..47bad3c73 100644
--- a/src/Request/Executor.php
+++ b/src/Request/Executor.php
@@ -13,6 +13,7 @@
 use GraphQL\Validator\Rules\DisableIntrospection;
 use GraphQL\Validator\Rules\QueryComplexity;
 use GraphQL\Validator\Rules\QueryDepth;
+use Overblog\GraphQLBundle\Definition\Type\ExtensibleSchema;
 use Overblog\GraphQLBundle\Event\Events;
 use Overblog\GraphQLBundle\Event\ExecutorArgumentsEvent;
 use Overblog\GraphQLBundle\Event\ExecutorContextEvent;
@@ -22,14 +23,22 @@
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
+use Symfony\Contracts\Service\ResetInterface;
 use function array_keys;
 use function is_callable;
 use function sprintf;
 
-class Executor
+class Executor implements ResetInterface
 {
     public const PROMISE_ADAPTER_SERVICE_ID = 'overblog_graphql.promise_adapter';
 
+    /**
+     * @var array<Closure>
+     */
+    private array $schemaBuilders = [];
+    /**
+     * @var array<Schema>
+     */
     private array $schemas = [];
     private EventDispatcherInterface $dispatcher;
     private PromiseAdapter $promiseAdapter;
@@ -61,7 +70,7 @@ public function setExecutor(ExecutorInterface $executor): self
 
     public function addSchemaBuilder(string $name, Closure $builder): self
     {
-        $this->schemas[$name] = $builder;
+        $this->schemaBuilders[$name] = $builder;
 
         return $this;
     }
@@ -75,7 +84,7 @@ public function addSchema(string $name, Schema $schema): self
 
     public function getSchema(string $name = null): Schema
     {
-        if (empty($this->schemas)) {
+        if (empty($this->schemaBuilders) && empty($this->schemas)) {
             throw new RuntimeException('At least one schema should be declare.');
         }
 
@@ -83,19 +92,30 @@ public function getSchema(string $name = null): Schema
             $name = isset($this->schemas['default']) ? 'default' : array_key_first($this->schemas);
         }
 
-        if (!isset($this->schemas[$name])) {
-            throw new NotFoundHttpException(sprintf('Could not find "%s" schema.', $name));
-        }
+        if (isset($this->schemas[$name])) {
+            $schema = $this->schemas[$name];
+        } elseif (isset($this->schemaBuilders[$name])) {
+            $schema = call_user_func($this->schemaBuilders[$name]);
 
-        $schema = $this->schemas[$name];
-        if (is_callable($schema)) {
-            $schema = $schema();
             $this->addSchema((string) $name, $schema);
+        } else {
+            throw new NotFoundHttpException(sprintf('Could not find "%s" schema.', $name));
         }
 
         return $schema;
     }
 
+    public function reset(): void
+    {
+        // Remove only ExtensibleSchema and isResettable
+        $this->schemas = array_filter(
+            $this->schemas,
+            function (Schema $schema) {
+                return $schema instanceof ExtensibleSchema && !$schema->isResettable();
+            }
+        );
+    }
+
     public function getSchemasNames(): array
     {
         return array_keys($this->schemas);

From 4b7dd332d9e0d517e2fee3cf9b05e9572df434d1 Mon Sep 17 00:00:00 2001
From: Vladyslav Lyshenko <vladyslav.lyshenko@entuasol.com>
Date: Sun, 4 Feb 2024 17:45:49 +0200
Subject: [PATCH 4/7] fix: resettable schemas

---
 src/Request/Executor.php | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/Request/Executor.php b/src/Request/Executor.php
index 47bad3c73..87e319817 100644
--- a/src/Request/Executor.php
+++ b/src/Request/Executor.php
@@ -92,6 +92,10 @@ public function getSchema(string $name = null): Schema
             $name = isset($this->schemas['default']) ? 'default' : array_key_first($this->schemas);
         }
 
+        if (null === $name) {
+            $name = isset($this->schemaBuilders['default']) ? 'default' : array_key_first($this->schemaBuilders);
+        }
+
         if (isset($this->schemas[$name])) {
             $schema = $this->schemas[$name];
         } elseif (isset($this->schemaBuilders[$name])) {
@@ -118,7 +122,7 @@ function (Schema $schema) {
 
     public function getSchemasNames(): array
     {
-        return array_keys($this->schemas);
+        return array_merge(array_keys($this->schemaBuilders), array_keys($this->schemas));
     }
 
     public function setMaxQueryDepth(int $maxQueryDepth): void

From 4028909aad7a601075bb5b88e97afcd17e56dfc4 Mon Sep 17 00:00:00 2001
From: Vladyslav Lyshenko <vladyslav.lyshenko@entuasol.com>
Date: Sun, 4 Feb 2024 17:54:46 +0200
Subject: [PATCH 5/7] fix: resettable schemas

---
 src/Definition/Type/ExtensibleSchema.php | 1 -
 src/Request/Executor.php                 | 6 +-----
 2 files changed, 1 insertion(+), 6 deletions(-)

diff --git a/src/Definition/Type/ExtensibleSchema.php b/src/Definition/Type/ExtensibleSchema.php
index cbab2fcbb..e8b267975 100644
--- a/src/Definition/Type/ExtensibleSchema.php
+++ b/src/Definition/Type/ExtensibleSchema.php
@@ -12,7 +12,6 @@ class ExtensibleSchema extends Schema
 {
     /**
      * Need to reset when container reset called
-     * @var bool
      */
     private bool $isResettable = false;
 
diff --git a/src/Request/Executor.php b/src/Request/Executor.php
index 87e319817..4c808a2ae 100644
--- a/src/Request/Executor.php
+++ b/src/Request/Executor.php
@@ -22,10 +22,8 @@
 use RuntimeException;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
-
 use Symfony\Contracts\Service\ResetInterface;
 use function array_keys;
-use function is_callable;
 use function sprintf;
 
 class Executor implements ResetInterface
@@ -114,9 +112,7 @@ public function reset(): void
         // Remove only ExtensibleSchema and isResettable
         $this->schemas = array_filter(
             $this->schemas,
-            function (Schema $schema) {
-                return $schema instanceof ExtensibleSchema && !$schema->isResettable();
-            }
+            fn (Schema $schema) => $schema instanceof ExtensibleSchema && !$schema->isResettable()
         );
     }
 

From 7020546bf51b448260e487d4bd5f94857e883f5d Mon Sep 17 00:00:00 2001
From: Vladyslav Lyshenko <vladyslav.lyshenko@entuasol.com>
Date: Mon, 5 Feb 2024 14:27:21 +0200
Subject: [PATCH 6/7] fix: resettable schemas

---
 src/Resources/config/services.yaml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml
index 915b24e12..c3f7ab843 100644
--- a/src/Resources/config/services.yaml
+++ b/src/Resources/config/services.yaml
@@ -21,6 +21,8 @@ services:
         calls:
             - ["setMaxQueryComplexity", ["%overblog_graphql.query_max_complexity%"]]
             - ["setMaxQueryDepth", ["%overblog_graphql.query_max_depth%"]]
+        tags:
+            - {name: 'kernel.reset', 'method': "reset"}
 
     Overblog\GraphQLBundle\Definition\Builder\SchemaBuilder:
         arguments:

From 215c7d4eeafa8eb9973e76be5f3d622d04e7fc4c Mon Sep 17 00:00:00 2001
From: Vladyslav Lyshenko <vladyslav.lyshenko@entuasol.com>
Date: Tue, 6 Feb 2024 14:38:12 +0200
Subject: [PATCH 7/7] fix: resettable schemas

---
 src/Definition/Builder/SchemaBuilder.php | 19 ++++++++++++++-----
 src/Resolver/AbstractResolver.php        | 24 ++++++++++++++----------
 src/Resolver/TypeResolver.php            |  7 +++++++
 src/Resources/config/services.yaml       |  3 +++
 4 files changed, 38 insertions(+), 15 deletions(-)

diff --git a/src/Definition/Builder/SchemaBuilder.php b/src/Definition/Builder/SchemaBuilder.php
index ec62b6a7a..8f35b33a5 100644
--- a/src/Definition/Builder/SchemaBuilder.php
+++ b/src/Definition/Builder/SchemaBuilder.php
@@ -10,12 +10,14 @@
 use Overblog\GraphQLBundle\Definition\Type\SchemaExtension\ValidatorExtension;
 use Overblog\GraphQLBundle\Resolver\TypeResolver;
 
+use Symfony\Contracts\Service\ResetInterface;
 use function array_map;
 
-final class SchemaBuilder
+final class SchemaBuilder implements ResetInterface
 {
     private TypeResolver $typeResolver;
     private bool $enableValidation;
+    private array $builders = [];
 
     public function __construct(TypeResolver $typeResolver, bool $enableValidation = false)
     {
@@ -26,12 +28,11 @@ public function __construct(TypeResolver $typeResolver, bool $enableValidation =
     public function getBuilder(string $name, ?string $queryAlias, string $mutationAlias = null, string $subscriptionAlias = null, array $types = [], bool $resettable = false): Closure
     {
         return function () use ($name, $queryAlias, $mutationAlias, $subscriptionAlias, $types, $resettable): ExtensibleSchema {
-            static $schema = null;
-            if (null === $schema) {
-                $schema = $this->create($name, $queryAlias, $mutationAlias, $subscriptionAlias, $types, $resettable);
+            if (!isset($this->builders[$name])) {
+                $this->builders[$name] = $this->create($name, $queryAlias, $mutationAlias, $subscriptionAlias, $types, $resettable);
             }
 
-            return $schema;
+            return $this->builders[$name];
         };
     }
 
@@ -75,4 +76,12 @@ private function buildSchemaArguments(string $schemaName, Type $query, ?Type $mu
             },
         ];
     }
+
+    public function reset(): void
+    {
+        $this->builders = array_filter(
+            $this->builders,
+            fn (ExtensibleSchema $schema) => false === $schema->isResettable()
+        );
+    }
 }
diff --git a/src/Resolver/AbstractResolver.php b/src/Resolver/AbstractResolver.php
index b0deb7a54..7d81d9c9d 100644
--- a/src/Resolver/AbstractResolver.php
+++ b/src/Resolver/AbstractResolver.php
@@ -4,21 +4,21 @@
 
 namespace Overblog\GraphQLBundle\Resolver;
 
+use Symfony\Contracts\Service\ResetInterface;
 use function array_keys;
 
-abstract class AbstractResolver implements FluentResolverInterface
+abstract class AbstractResolver implements FluentResolverInterface, ResetInterface
 {
+    private array $solutionsFactory = [];
     private array $solutions = [];
     private array $aliases = [];
     private array $solutionOptions = [];
-    private array $fullyLoadedSolutions = [];
 
     public function addSolution(string $id, callable $factory, array $aliases = [], array $options = []): self
     {
-        $this->fullyLoadedSolutions[$id] = false;
         $this->addAliases($id, $aliases);
 
-        $this->solutions[$id] = $factory;
+        $this->solutionsFactory[$id] = $factory;
         $this->solutionOptions[$id] = $options;
 
         return $this;
@@ -28,7 +28,7 @@ public function hasSolution(string $id): bool
     {
         $id = $this->resolveAlias($id);
 
-        return isset($this->solutions[$id]);
+        return isset($this->solutionsFactory[$id]);
     }
 
     /**
@@ -81,13 +81,12 @@ private function loadSolution(string $id)
             return null;
         }
 
-        if ($this->fullyLoadedSolutions[$id]) {
+        if (isset($this->solutions[$id])) {
             return $this->solutions[$id];
         } else {
-            $loader = $this->solutions[$id];
+            $loader = $this->solutionsFactory[$id];
             $this->solutions[$id] = $solution = $loader();
             $this->onLoadSolution($solution);
-            $this->fullyLoadedSolutions[$id] = true;
 
             return $solution;
         }
@@ -110,10 +109,15 @@ private function resolveAlias(string $alias): string
      */
     private function loadSolutions(): array
     {
-        foreach ($this->solutions as $name => &$solution) {
-            $solution = $this->loadSolution($name);
+        foreach (array_keys($this->solutionsFactory) as $name) {
+            $this->loadSolution($name);
         }
 
         return $this->solutions;
     }
+
+    public function reset(): void
+    {
+        $this->solutions = [];
+    }
 }
diff --git a/src/Resolver/TypeResolver.php b/src/Resolver/TypeResolver.php
index c1769922a..39fd87030 100644
--- a/src/Resolver/TypeResolver.php
+++ b/src/Resolver/TypeResolver.php
@@ -87,4 +87,11 @@ protected function supportedSolutionClass(): ?string
     {
         return Type::class;
     }
+
+    public function reset(): void
+    {
+        parent::reset();
+
+        $this->cache = [];
+    }
 }
diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml
index c3f7ab843..aabc71fa0 100644
--- a/src/Resources/config/services.yaml
+++ b/src/Resources/config/services.yaml
@@ -28,6 +28,8 @@ services:
         arguments:
             - '@Overblog\GraphQLBundle\Resolver\TypeResolver'
             - false
+        tags:
+            - { name: 'kernel.reset', 'method': "reset" }
 
     Overblog\GraphQLBundle\Definition\Builder\TypeFactory:
         arguments:
@@ -39,6 +41,7 @@ services:
             - ['setDispatcher', ['@event_dispatcher']]
         tags:
             - { name: overblog_graphql.service, alias: typeResolver }
+            - {name: 'kernel.reset', 'method': "reset"}
 
     Overblog\GraphQLBundle\Transformer\ArgumentsTransformer:
         arguments: