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: