From 47530c73e4d6c55b957d785971273b50d67c1793 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Mon, 14 Aug 2023 10:48:32 +0200 Subject: [PATCH 1/9] Introduce StandardTypeRegistry Currently, types are referenced and cached statically. This is problematic when using multiple schema's that have different standard types that share the same memory. For example, when running them in un-isolated unit tests or when there is a long running PHP process that serves GraphQL requests. To solve this problem, we introduce a `StandardTypeRegistry` interface with a `DefaultTypeRegistry` implementation. People are allowed to create their own registries by implementing the interface. Every Schema should be constructed with a `typeRegistry` that is an instance of `StandardTypeRegistry`. From there on, all types are queried from the registry. The registry will be responsible for caching the types to make sure subsequent calls to the same type will return the same instance. Internally, all static calls to the standard types (Type::string(), Type::int(), Type::float(), Type::boolean(), Type::id()) have been replaced with dynamic calls on the type registry. Also calls to the introspection objects and internal directives are using the type registry now. As most people probably have only one schema, we keep the static methods on the Type call. These now forward to `DefaultTypeRegistry::getInstance()`. This way, we don't break existing installations. The reason for creating a `StandardTypeRegistry` interface as opposed to just a non-final implementation is that it allows people to use composition instead of inheritance to extend the functionality of the registry. For example, in my project I'd like to have a registry that holds all types and that allows me to query any type by their name (instead of FQCN). I can then delegate the interface methods to the decorated `StandardTypeRegistry`. Resolves #1424 --- docs/class-reference.md | 11 +- src/Executor/ReferenceExecutor.php | 26 +- src/Type/Definition/Directive.php | 90 +------ src/Type/Definition/Type.php | 53 ++-- src/Type/Introspection.php | 166 ++++++------ .../Registry/DefaultStandardTypeRegistry.php | 236 ++++++++++++++++++ src/Type/Registry/StandardTypeRegistry.php | 64 +++++ src/Type/Schema.php | 21 +- src/Type/SchemaConfig.php | 20 ++ src/Utils/ASTDefinitionBuilder.php | 19 +- src/Utils/BuildClientSchema.php | 20 +- src/Utils/BuildSchema.php | 30 ++- src/Utils/SchemaExtender.php | 9 +- src/Utils/SchemaPrinter.php | 3 +- src/Utils/TypeInfo.php | 7 +- .../Rules/KnownArgumentNamesOnDirectives.php | 4 +- src/Validator/Rules/KnownDirectives.php | 4 +- .../ProvidedRequiredArgumentsOnDirectives.php | 4 +- src/Validator/Rules/QueryComplexity.php | 4 +- src/Validator/Rules/QuerySecurityRule.php | 7 +- .../Rules/UniqueDirectivesPerLocation.php | 4 +- tests/CustomIntType.php | 7 + tests/DefaultStandardTypeRegistryTest.php | 55 ++++ tests/MultipleSchemaTest.php | 53 ++++ tests/Type/StandardTypesTest.php | 16 +- tests/Utils/BreakingChangesFinderTest.php | 13 +- tests/Utils/BuildSchemaTest.php | 24 +- tests/Validator/ValidatorTestCase.php | 9 +- 28 files changed, 678 insertions(+), 301 deletions(-) create mode 100644 src/Type/Registry/DefaultStandardTypeRegistry.php create mode 100644 src/Type/Registry/StandardTypeRegistry.php create mode 100644 tests/CustomIntType.php create mode 100644 tests/DefaultStandardTypeRegistryTest.php create mode 100644 tests/MultipleSchemaTest.php diff --git a/docs/class-reference.md b/docs/class-reference.md index 1577d3c35..fc82356f0 100644 --- a/docs/class-reference.md +++ b/docs/class-reference.md @@ -548,6 +548,7 @@ typeLoader?: TypeLoader|null, assumeValid?: bool|null, astNode?: SchemaDefinitionNode|null, extensionASTNodes?: array|null, +typeRegistry?: StandardTypeRegistry|null, } ### GraphQL\Type\SchemaConfig Methods @@ -2429,7 +2430,12 @@ assumeValidSDL?: bool * @throws InvariantViolation * @throws SyntaxError */ -static function build($source, ?callable $typeConfigDecorator = null, array $options = []): GraphQL\Type\Schema +static function build( + $source, + ?callable $typeConfigDecorator = null, + array $options = [], + ?GraphQL\Type\Registry\StandardTypeRegistry $typeRegistry = null +): GraphQL\Type\Schema ``` ```php @@ -2457,7 +2463,8 @@ static function build($source, ?callable $typeConfigDecorator = null, array $opt static function buildAST( GraphQL\Language\AST\DocumentNode $ast, ?callable $typeConfigDecorator = null, - array $options = [] + array $options = [], + ?GraphQL\Type\Registry\StandardTypeRegistry $typeRegistry = null ): GraphQL\Type\Schema ``` diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index fa3c63379..2ae88294e 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -17,7 +17,6 @@ use GraphQL\Language\AST\SelectionNode; use GraphQL\Language\AST\SelectionSetNode; use GraphQL\Type\Definition\AbstractType; -use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\FieldDefinition; use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\LeafType; @@ -467,7 +466,7 @@ protected function shouldIncludeNode(SelectionNode $node): bool $variableValues = $this->exeContext->variableValues; $skip = Values::getDirectiveValues( - Directive::skipDirective(), + $this->exeContext->schema->typeRegistry->skipDirective(), $node, $variableValues ); @@ -476,7 +475,7 @@ protected function shouldIncludeNode(SelectionNode $node): bool } $include = Values::getDirectiveValues( - Directive::includeDirective(), + $this->exeContext->schema->typeRegistry->includeDirective(), $node, $variableValues ); @@ -661,23 +660,20 @@ protected function resolveField(ObjectType $parentType, $rootValue, \ArrayObject */ protected function getFieldDef(Schema $schema, ObjectType $parentType, string $fieldName): ?FieldDefinition { - static $schemaMetaFieldDef, $typeMetaFieldDef, $typeNameMetaFieldDef; - $schemaMetaFieldDef ??= Introspection::schemaMetaFieldDef(); - $typeMetaFieldDef ??= Introspection::typeMetaFieldDef(); - $typeNameMetaFieldDef ??= Introspection::typeNameMetaFieldDef(); - $queryType = $schema->getQueryType(); - - if ($fieldName === $schemaMetaFieldDef->name && $queryType === $parentType) { - return $schemaMetaFieldDef; + $schemaMeta = $schema->typeRegistry->introspection()->schemaMetaFieldDef(); + if ($fieldName === $schemaMeta->name && $queryType === $parentType) { + return $schemaMeta; } - if ($fieldName === $typeMetaFieldDef->name && $queryType === $parentType) { - return $typeMetaFieldDef; + $typeMeta = $schema->typeRegistry->introspection()->typeMetaFieldDef(); + if ($fieldName === $typeMeta->name && $queryType === $parentType) { + return $typeMeta; } - if ($fieldName === $typeNameMetaFieldDef->name) { - return $typeNameMetaFieldDef; + $typeNameMeta = $schema->typeRegistry->introspection()->typeNameMetaFieldDef(); + if ($fieldName === $typeNameMeta->name) { + return $typeNameMeta; } return $parentType->findField($fieldName); diff --git a/src/Type/Definition/Directive.php b/src/Type/Definition/Directive.php index 0a6db42e5..c9934ad70 100644 --- a/src/Type/Definition/Directive.php +++ b/src/Type/Definition/Directive.php @@ -4,7 +4,7 @@ use GraphQL\Error\InvariantViolation; use GraphQL\Language\AST\DirectiveDefinitionNode; -use GraphQL\Language\DirectiveLocation; +use GraphQL\Type\Registry\DefaultStandardTypeRegistry; /** * @phpstan-import-type ArgumentListConfig from Argument @@ -28,12 +28,11 @@ class Directive public const DEPRECATED_NAME = 'deprecated'; public const REASON_ARGUMENT_NAME = 'reason'; - /** - * Lazily initialized. - * - * @var array - */ - protected static array $internalDirectives; + public const INTERNAL_DIRECTIVE_NAMES = [ + self::INCLUDE_NAME, + self::SKIP_NAME, + self::DEPRECATED_NAME, + ]; public string $name; @@ -75,14 +74,6 @@ public function __construct(array $config) $this->config = $config; } - /** @throws InvariantViolation */ - public static function includeDirective(): Directive - { - $internal = self::getInternalDirectives(); - - return $internal['include']; - } - /** * @throws InvariantViolation * @@ -90,76 +81,11 @@ public static function includeDirective(): Directive */ public static function getInternalDirectives(): array { - return self::$internalDirectives ??= [ - 'include' => new self([ - 'name' => self::INCLUDE_NAME, - 'description' => 'Directs the executor to include this field or fragment only when the `if` argument is true.', - 'locations' => [ - DirectiveLocation::FIELD, - DirectiveLocation::FRAGMENT_SPREAD, - DirectiveLocation::INLINE_FRAGMENT, - ], - 'args' => [ - self::IF_ARGUMENT_NAME => [ - 'type' => Type::nonNull(Type::boolean()), - 'description' => 'Included when true.', - ], - ], - ]), - 'skip' => new self([ - 'name' => self::SKIP_NAME, - 'description' => 'Directs the executor to skip this field or fragment when the `if` argument is true.', - 'locations' => [ - DirectiveLocation::FIELD, - DirectiveLocation::FRAGMENT_SPREAD, - DirectiveLocation::INLINE_FRAGMENT, - ], - 'args' => [ - self::IF_ARGUMENT_NAME => [ - 'type' => Type::nonNull(Type::boolean()), - 'description' => 'Skipped when true.', - ], - ], - ]), - 'deprecated' => new self([ - 'name' => self::DEPRECATED_NAME, - 'description' => 'Marks an element of a GraphQL schema as no longer supported.', - 'locations' => [ - DirectiveLocation::FIELD_DEFINITION, - DirectiveLocation::ENUM_VALUE, - DirectiveLocation::ARGUMENT_DEFINITION, - DirectiveLocation::INPUT_FIELD_DEFINITION, - ], - 'args' => [ - self::REASON_ARGUMENT_NAME => [ - 'type' => Type::string(), - 'description' => 'Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax, as specified by [CommonMark](https://commonmark.org/).', - 'defaultValue' => self::DEFAULT_DEPRECATION_REASON, - ], - ], - ]), - ]; - } - - /** @throws InvariantViolation */ - public static function skipDirective(): Directive - { - $internal = self::getInternalDirectives(); - - return $internal['skip']; - } - - /** @throws InvariantViolation */ - public static function deprecatedDirective(): Directive - { - $internal = self::getInternalDirectives(); - - return $internal['deprecated']; + return DefaultStandardTypeRegistry::instance()->internalDirectives(); } - /** @throws InvariantViolation */ public static function isSpecifiedDirective(Directive $directive): bool { - return \array_key_exists($directive->name, self::getInternalDirectives()); + return \in_array($directive->name, self::INTERNAL_DIRECTIVE_NAMES, true); } } diff --git a/src/Type/Definition/Type.php b/src/Type/Definition/Type.php index cd734b99c..390b1719d 100644 --- a/src/Type/Definition/Type.php +++ b/src/Type/Definition/Type.php @@ -4,6 +4,7 @@ use GraphQL\Error\InvariantViolation; use GraphQL\Type\Introspection; +use GraphQL\Type\Registry\DefaultStandardTypeRegistry; use GraphQL\Utils\Utils; /** @@ -30,9 +31,6 @@ abstract class Type implements \JsonSerializable ...Introspection::TYPE_NAMES, ]; - /** @var array */ - protected static array $standardTypes; - /** * @api * @@ -40,7 +38,7 @@ abstract class Type implements \JsonSerializable */ public static function int(): ScalarType { - return static::$standardTypes[self::INT] ??= new IntType(); + return DefaultStandardTypeRegistry::instance()->int(); } /** @@ -50,7 +48,7 @@ public static function int(): ScalarType */ public static function float(): ScalarType { - return static::$standardTypes[self::FLOAT] ??= new FloatType(); + return DefaultStandardTypeRegistry::instance()->float(); } /** @@ -60,7 +58,7 @@ public static function float(): ScalarType */ public static function string(): ScalarType { - return static::$standardTypes[self::STRING] ??= new StringType(); + return DefaultStandardTypeRegistry::instance()->string(); } /** @@ -70,7 +68,7 @@ public static function string(): ScalarType */ public static function boolean(): ScalarType { - return static::$standardTypes[self::BOOLEAN] ??= new BooleanType(); + return DefaultStandardTypeRegistry::instance()->boolean(); } /** @@ -80,7 +78,7 @@ public static function boolean(): ScalarType */ public static function id(): ScalarType { - return static::$standardTypes[self::ID] ??= new IDType(); + return DefaultStandardTypeRegistry::instance()->id(); } /** @@ -107,23 +105,6 @@ public static function nonNull($type): NonNull return new NonNull($type); } - /** - * Returns all builtin in types including base scalar and introspection types. - * - * @throws InvariantViolation - * - * @return array - */ - public static function builtInTypes(): array - { - static $builtInTypes; - - return $builtInTypes ??= \array_merge( - Introspection::getTypes(), - self::getStandardTypes() - ); - } - /** * Returns all builtin scalar types. * @@ -133,22 +114,20 @@ public static function builtInTypes(): array */ public static function getStandardTypes(): array { - return [ - self::INT => static::int(), - self::FLOAT => static::float(), - self::STRING => static::string(), - self::BOOLEAN => static::boolean(), - self::ID => static::id(), - ]; + return DefaultStandardTypeRegistry::instance()->standardTypes(); } /** + * @deprecated use a custom instance of `DefaultStandardTypeRegistry` on the `Schema` instead + * * @param array $types * * @throws InvariantViolation */ public static function overrideStandardTypes(array $types): void { + $standardTypes = DefaultStandardTypeRegistry::instance()->standardTypes(); + foreach ($types as $type) { // @phpstan-ignore-next-line generic type is not enforced by PHP if (! $type instanceof ScalarType) { @@ -163,8 +142,16 @@ public static function overrideStandardTypes(array $types): void throw new InvariantViolation("Expecting one of the following names for a standard type: {$standardTypeNames}; got {$notStandardTypeName}"); } - static::$standardTypes[$type->name] = $type; + $standardTypes[$type->name] = $type; } + + DefaultStandardTypeRegistry::register(new DefaultStandardTypeRegistry( + $standardTypes[Type::INT], + $standardTypes[Type::FLOAT], + $standardTypes[Type::STRING], + $standardTypes[Type::BOOLEAN], + $standardTypes[Type::ID], + )); } /** diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index ab6a39b98..fad3a95b0 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -23,6 +23,7 @@ use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\WrappingType; +use GraphQL\Type\Registry\StandardTypeRegistry; use GraphQL\Utils\AST; use GraphQL\Utils\Utils; @@ -69,7 +70,14 @@ class Introspection ]; /** @var array */ - private static $map = []; + private array $cache = []; + + private StandardTypeRegistry $typeRegistry; + + public function __construct(StandardTypeRegistry $typeRegistry) + { + $this->typeRegistry = $typeRegistry; + } /** * @param IntrospectionOptions $options @@ -236,24 +244,24 @@ public static function isIntrospectionType(NamedType $type): bool * * @return array */ - public static function getTypes(): array + public function getTypes(): array { return [ - self::SCHEMA_OBJECT_NAME => self::_schema(), - self::TYPE_OBJECT_NAME => self::_type(), - self::DIRECTIVE_OBJECT_NAME => self::_directive(), - self::FIELD_OBJECT_NAME => self::_field(), - self::INPUT_VALUE_OBJECT_NAME => self::_inputValue(), - self::ENUM_VALUE_OBJECT_NAME => self::_enumValue(), - self::TYPE_KIND_ENUM_NAME => self::_typeKind(), - self::DIRECTIVE_LOCATION_ENUM_NAME => self::_directiveLocation(), + self::SCHEMA_OBJECT_NAME => $this->_schema(), + self::TYPE_OBJECT_NAME => $this->_type(), + self::DIRECTIVE_OBJECT_NAME => $this->_directive(), + self::FIELD_OBJECT_NAME => $this->_field(), + self::INPUT_VALUE_OBJECT_NAME => $this->_inputValue(), + self::ENUM_VALUE_OBJECT_NAME => $this->_enumValue(), + self::TYPE_KIND_ENUM_NAME => $this->_typeKind(), + self::DIRECTIVE_LOCATION_ENUM_NAME => $this->_directiveLocation(), ]; } /** @throws InvariantViolation */ - public static function _schema(): ObjectType + public function _schema(): ObjectType { - return self::$map[self::SCHEMA_OBJECT_NAME] ??= new ObjectType([ + return $this->cache[self::SCHEMA_OBJECT_NAME] ??= new ObjectType([ 'name' => self::SCHEMA_OBJECT_NAME, 'isIntrospection' => true, 'description' => 'A GraphQL Schema defines the capabilities of a GraphQL ' @@ -263,27 +271,27 @@ public static function _schema(): ObjectType 'fields' => [ 'types' => [ 'description' => 'A list of all types supported by this server.', - 'type' => new NonNull(new ListOfType(new NonNull(self::_type()))), + 'type' => new NonNull(new ListOfType(new NonNull($this->_type()))), 'resolve' => static fn (Schema $schema): array => $schema->getTypeMap(), ], 'queryType' => [ 'description' => 'The type that query operations will be rooted at.', - 'type' => new NonNull(self::_type()), + 'type' => new NonNull($this->_type()), 'resolve' => static fn (Schema $schema): ?ObjectType => $schema->getQueryType(), ], 'mutationType' => [ 'description' => 'If this server supports mutation, the type that mutation operations will be rooted at.', - 'type' => self::_type(), + 'type' => $this->_type(), 'resolve' => static fn (Schema $schema): ?ObjectType => $schema->getMutationType(), ], 'subscriptionType' => [ 'description' => 'If this server support subscription, the type that subscription operations will be rooted at.', - 'type' => self::_type(), + 'type' => $this->_type(), 'resolve' => static fn (Schema $schema): ?ObjectType => $schema->getSubscriptionType(), ], 'directives' => [ 'description' => 'A list of all directives supported by this server.', - 'type' => Type::nonNull(Type::listOf(Type::nonNull(self::_directive()))), + 'type' => Type::nonNull(Type::listOf(Type::nonNull($this->_directive()))), 'resolve' => static fn (Schema $schema): array => $schema->getDirectives(), ], ], @@ -291,9 +299,9 @@ public static function _schema(): ObjectType } /** @throws InvariantViolation */ - public static function _type(): ObjectType + public function _type(): ObjectType { - return self::$map[self::TYPE_OBJECT_NAME] ??= new ObjectType([ + return $this->cache[self::TYPE_OBJECT_NAME] ??= new ObjectType([ 'name' => self::TYPE_OBJECT_NAME, 'isIntrospection' => true, 'description' => 'The fundamental unit of any GraphQL Schema is the type. There are ' @@ -305,10 +313,10 @@ public static function _type(): ObjectType . 'Object and Interface types provide the fields they describe. Abstract ' . 'types, Union and Interface, provide the Object types possible ' . 'at runtime. List and NonNull types compose other types.', - 'fields' => static fn (): array => [ + 'fields' => fn (): array => [ 'kind' => [ - 'type' => Type::nonNull(self::_typeKind()), - 'resolve' => static function (Type $type): string { + 'type' => Type::nonNull($this->_typeKind()), + 'resolve' => function (Type $type): string { switch (true) { case $type instanceof ListOfType: return TypeKind::LIST; @@ -333,22 +341,22 @@ public static function _type(): ObjectType }, ], 'name' => [ - 'type' => Type::string(), + 'type' => $this->typeRegistry->string(), 'resolve' => static fn (Type $type): ?string => $type instanceof NamedType ? $type->name : null, ], 'description' => [ - 'type' => Type::string(), + 'type' => $this->typeRegistry->string(), 'resolve' => static fn (Type $type): ?string => $type instanceof NamedType ? $type->description : null, ], 'fields' => [ - 'type' => Type::listOf(Type::nonNull(self::_field())), + 'type' => Type::listOf(Type::nonNull($this->_field())), 'args' => [ 'includeDeprecated' => [ - 'type' => Type::boolean(), + 'type' => $this->typeRegistry->boolean(), 'defaultValue' => false, ], ], @@ -371,22 +379,22 @@ public static function _type(): ObjectType }, ], 'interfaces' => [ - 'type' => Type::listOf(Type::nonNull(self::_type())), + 'type' => Type::listOf(Type::nonNull($this->_type())), 'resolve' => static fn ($type): ?array => $type instanceof ObjectType || $type instanceof InterfaceType ? $type->getInterfaces() : null, ], 'possibleTypes' => [ - 'type' => Type::listOf(Type::nonNull(self::_type())), + 'type' => Type::listOf(Type::nonNull($this->_type())), 'resolve' => static fn ($type, $args, $context, ResolveInfo $info): ?array => $type instanceof InterfaceType || $type instanceof UnionType ? $info->schema->getPossibleTypes($type) : null, ], 'enumValues' => [ - 'type' => Type::listOf(Type::nonNull(self::_enumValue())), + 'type' => Type::listOf(Type::nonNull($this->_enumValue())), 'args' => [ 'includeDeprecated' => [ - 'type' => Type::boolean(), + 'type' => $this->typeRegistry->boolean(), 'defaultValue' => false, ], ], @@ -411,10 +419,10 @@ static function (EnumValueDefinition $value): bool { }, ], 'inputFields' => [ - 'type' => Type::listOf(Type::nonNull(self::_inputValue())), + 'type' => Type::listOf(Type::nonNull($this->_inputValue())), 'args' => [ 'includeDeprecated' => [ - 'type' => Type::boolean(), + 'type' => $this->typeRegistry->boolean(), 'defaultValue' => false, ], ], @@ -437,7 +445,7 @@ static function (EnumValueDefinition $value): bool { }, ], 'ofType' => [ - 'type' => self::_type(), + 'type' => $this->_type(), 'resolve' => static fn ($type): ?Type => $type instanceof WrappingType ? $type->getWrappedType() : null, @@ -447,9 +455,9 @@ static function (EnumValueDefinition $value): bool { } /** @throws InvariantViolation */ - public static function _typeKind(): EnumType + public function _typeKind(): EnumType { - return self::$map[self::TYPE_KIND_ENUM_NAME] ??= new EnumType([ + return $this->cache[self::TYPE_KIND_ENUM_NAME] ??= new EnumType([ 'name' => self::TYPE_KIND_ENUM_NAME, 'isIntrospection' => true, 'description' => 'An enum describing what kind of type a given `__Type` is.', @@ -491,27 +499,27 @@ public static function _typeKind(): EnumType } /** @throws InvariantViolation */ - public static function _field(): ObjectType + public function _field(): ObjectType { - return self::$map[self::FIELD_OBJECT_NAME] ??= new ObjectType([ + return $this->cache[self::FIELD_OBJECT_NAME] ??= new ObjectType([ 'name' => self::FIELD_OBJECT_NAME, 'isIntrospection' => true, 'description' => 'Object and Interface types are described by a list of Fields, each of ' . 'which has a name, potentially a list of arguments, and a return type.', - 'fields' => static fn (): array => [ + 'fields' => fn (): array => [ 'name' => [ - 'type' => Type::nonNull(Type::string()), + 'type' => Type::nonNull($this->typeRegistry->string()), 'resolve' => static fn (FieldDefinition $field): string => $field->name, ], 'description' => [ - 'type' => Type::string(), + 'type' => $this->typeRegistry->string(), 'resolve' => static fn (FieldDefinition $field): ?string => $field->description, ], 'args' => [ - 'type' => Type::nonNull(Type::listOf(Type::nonNull(self::_inputValue()))), + 'type' => Type::nonNull(Type::listOf(Type::nonNull($this->_inputValue()))), 'args' => [ 'includeDeprecated' => [ - 'type' => Type::boolean(), + 'type' => $this->typeRegistry->boolean(), 'defaultValue' => false, ], ], @@ -530,16 +538,16 @@ public static function _field(): ObjectType }, ], 'type' => [ - 'type' => Type::nonNull(self::_type()), + 'type' => Type::nonNull($this->_type()), 'resolve' => static fn (FieldDefinition $field): Type => $field->getType(), ], 'isDeprecated' => [ - 'type' => Type::nonNull(Type::boolean()), + 'type' => Type::nonNull($this->typeRegistry->boolean()), 'resolve' => static fn (FieldDefinition $field): bool => $field->deprecationReason !== null && $field->deprecationReason !== '', ], 'deprecationReason' => [ - 'type' => Type::string(), + 'type' => $this->typeRegistry->string(), 'resolve' => static fn (FieldDefinition $field): ?string => $field->deprecationReason, ], ], @@ -547,32 +555,32 @@ public static function _field(): ObjectType } /** @throws InvariantViolation */ - public static function _inputValue(): ObjectType + public function _inputValue(): ObjectType { - return self::$map[self::INPUT_VALUE_OBJECT_NAME] ??= new ObjectType([ + return $this->cache[self::INPUT_VALUE_OBJECT_NAME] ??= new ObjectType([ 'name' => self::INPUT_VALUE_OBJECT_NAME, 'isIntrospection' => true, 'description' => 'Arguments provided to Fields or Directives and the input fields of an ' . 'InputObject are represented as Input Values which describe their type ' . 'and optionally a default value.', - 'fields' => static fn (): array => [ + 'fields' => fn (): array => [ 'name' => [ - 'type' => Type::nonNull(Type::string()), + 'type' => Type::nonNull($this->typeRegistry->string()), /** @param Argument|InputObjectField $inputValue */ 'resolve' => static fn ($inputValue): string => $inputValue->name, ], 'description' => [ - 'type' => Type::string(), + 'type' => $this->typeRegistry->string(), /** @param Argument|InputObjectField $inputValue */ 'resolve' => static fn ($inputValue): ?string => $inputValue->description, ], 'type' => [ - 'type' => Type::nonNull(self::_type()), + 'type' => Type::nonNull($this->_type()), /** @param Argument|InputObjectField $inputValue */ 'resolve' => static fn ($inputValue): Type => $inputValue->getType(), ], 'defaultValue' => [ - 'type' => Type::string(), + 'type' => $this->typeRegistry->string(), 'description' => 'A GraphQL-formatted string representing the default value for this input value.', /** @param Argument|InputObjectField $inputValue */ 'resolve' => static function ($inputValue): ?string { @@ -591,13 +599,13 @@ public static function _inputValue(): ObjectType }, ], 'isDeprecated' => [ - 'type' => Type::nonNull(Type::boolean()), + 'type' => Type::nonNull($this->typeRegistry->boolean()), /** @param Argument|InputObjectField $inputValue */ 'resolve' => static fn ($inputValue): bool => $inputValue->deprecationReason !== null && $inputValue->deprecationReason !== '', ], 'deprecationReason' => [ - 'type' => Type::string(), + 'type' => $this->typeRegistry->string(), /** @param Argument|InputObjectField $inputValue */ 'resolve' => static fn ($inputValue): ?string => $inputValue->deprecationReason, ], @@ -606,9 +614,9 @@ public static function _inputValue(): ObjectType } /** @throws InvariantViolation */ - public static function _enumValue(): ObjectType + public function _enumValue(): ObjectType { - return self::$map[self::ENUM_VALUE_OBJECT_NAME] ??= new ObjectType([ + return $this->cache[self::ENUM_VALUE_OBJECT_NAME] ??= new ObjectType([ 'name' => self::ENUM_VALUE_OBJECT_NAME, 'isIntrospection' => true, 'description' => 'One possible value for a given Enum. Enum values are unique values, not ' @@ -616,20 +624,20 @@ public static function _enumValue(): ObjectType . 'returned in a JSON response as a string.', 'fields' => [ 'name' => [ - 'type' => Type::nonNull(Type::string()), + 'type' => Type::nonNull($this->typeRegistry->string()), 'resolve' => static fn (EnumValueDefinition $enumValue): string => $enumValue->name, ], 'description' => [ - 'type' => Type::string(), + 'type' => $this->typeRegistry->string(), 'resolve' => static fn (EnumValueDefinition $enumValue): ?string => $enumValue->description, ], 'isDeprecated' => [ - 'type' => Type::nonNull(Type::boolean()), + 'type' => Type::nonNull($this->typeRegistry->boolean()), 'resolve' => static fn (EnumValueDefinition $enumValue): bool => $enumValue->deprecationReason !== null && $enumValue->deprecationReason !== '', ], 'deprecationReason' => [ - 'type' => Type::string(), + 'type' => $this->typeRegistry->string(), 'resolve' => static fn (EnumValueDefinition $enumValue): ?string => $enumValue->deprecationReason, ], ], @@ -637,9 +645,9 @@ public static function _enumValue(): ObjectType } /** @throws InvariantViolation */ - public static function _directive(): ObjectType + public function _directive(): ObjectType { - return self::$map[self::DIRECTIVE_OBJECT_NAME] ??= new ObjectType([ + return $this->cache[self::DIRECTIVE_OBJECT_NAME] ??= new ObjectType([ 'name' => self::DIRECTIVE_OBJECT_NAME, 'isIntrospection' => true, 'description' => 'A Directive provides a way to describe alternate runtime execution and ' @@ -650,25 +658,25 @@ public static function _directive(): ObjectType . 'describing additional information to the executor.', 'fields' => [ 'name' => [ - 'type' => Type::nonNull(Type::string()), + 'type' => Type::nonNull($this->typeRegistry->string()), 'resolve' => static fn (Directive $directive): string => $directive->name, ], 'description' => [ - 'type' => Type::string(), + 'type' => $this->typeRegistry->string(), 'resolve' => static fn (Directive $directive): ?string => $directive->description, ], 'isRepeatable' => [ - 'type' => Type::nonNull(Type::boolean()), + 'type' => Type::nonNull($this->typeRegistry->boolean()), 'resolve' => static fn (Directive $directive): bool => $directive->isRepeatable, ], 'locations' => [ 'type' => Type::nonNull(Type::listOf(Type::nonNull( - self::_directiveLocation() + $this->_directiveLocation() ))), 'resolve' => static fn (Directive $directive): array => $directive->locations, ], 'args' => [ - 'type' => Type::nonNull(Type::listOf(Type::nonNull(self::_inputValue()))), + 'type' => Type::nonNull(Type::listOf(Type::nonNull($this->_inputValue()))), 'resolve' => static fn (Directive $directive): array => $directive->args, ], ], @@ -676,9 +684,9 @@ public static function _directive(): ObjectType } /** @throws InvariantViolation */ - public static function _directiveLocation(): EnumType + public function _directiveLocation(): EnumType { - return self::$map[self::DIRECTIVE_LOCATION_ENUM_NAME] ??= new EnumType([ + return $this->cache[self::DIRECTIVE_LOCATION_ENUM_NAME] ??= new EnumType([ 'name' => self::DIRECTIVE_LOCATION_ENUM_NAME, 'isIntrospection' => true, 'description' => 'A Directive can be adjacent to many parts of the GraphQL language, a ' @@ -765,11 +773,11 @@ public static function _directiveLocation(): EnumType } /** @throws InvariantViolation */ - public static function schemaMetaFieldDef(): FieldDefinition + public function schemaMetaFieldDef(): FieldDefinition { - return self::$map[self::SCHEMA_FIELD_NAME] ??= new FieldDefinition([ + return $this->cache[self::SCHEMA_FIELD_NAME] ??= new FieldDefinition([ 'name' => self::SCHEMA_FIELD_NAME, - 'type' => Type::nonNull(self::_schema()), + 'type' => Type::nonNull($this->_schema()), 'description' => 'Access the current type schema of this server.', 'args' => [], 'resolve' => static fn ($source, array $args, $context, ResolveInfo $info): Schema => $info->schema, @@ -777,16 +785,16 @@ public static function schemaMetaFieldDef(): FieldDefinition } /** @throws InvariantViolation */ - public static function typeMetaFieldDef(): FieldDefinition + public function typeMetaFieldDef(): FieldDefinition { - return self::$map[self::TYPE_FIELD_NAME] ??= new FieldDefinition([ + return $this->cache[self::TYPE_FIELD_NAME] ??= new FieldDefinition([ 'name' => self::TYPE_FIELD_NAME, - 'type' => self::_type(), + 'type' => $this->_type(), 'description' => 'Request the type information of a single type.', 'args' => [ [ 'name' => 'name', - 'type' => Type::nonNull(Type::string()), + 'type' => Type::nonNull($this->typeRegistry->string()), ], ], 'resolve' => static fn ($source, array $args, $context, ResolveInfo $info): ?Type => $info->schema->getType($args['name']), @@ -794,11 +802,11 @@ public static function typeMetaFieldDef(): FieldDefinition } /** @throws InvariantViolation */ - public static function typeNameMetaFieldDef(): FieldDefinition + public function typeNameMetaFieldDef(): FieldDefinition { - return self::$map[self::TYPE_NAME_FIELD_NAME] ??= new FieldDefinition([ + return $this->cache[self::TYPE_NAME_FIELD_NAME] ??= new FieldDefinition([ 'name' => self::TYPE_NAME_FIELD_NAME, - 'type' => Type::nonNull(Type::string()), + 'type' => Type::nonNull($this->typeRegistry->string()), 'description' => 'The name of the current Object type at runtime.', 'args' => [], 'resolve' => static fn ($source, array $args, $context, ResolveInfo $info): string => $info->parentType->name, diff --git a/src/Type/Registry/DefaultStandardTypeRegistry.php b/src/Type/Registry/DefaultStandardTypeRegistry.php new file mode 100644 index 000000000..706e9cc87 --- /dev/null +++ b/src/Type/Registry/DefaultStandardTypeRegistry.php @@ -0,0 +1,236 @@ + */ + protected array $directives = []; + + protected Introspection $introspection; + + /** + * @param ScalarType|class-string $intType + * @param ScalarType|class-string $floatType + * @param ScalarType|class-string $stringType + * @param ScalarType|class-string $booleanType + * @param ScalarType|class-string $idType + */ + public function __construct( + $intType = IntType::class, + $floatType = FloatType::class, + $stringType = StringType::class, + $booleanType = BooleanType::class, + $idType = IDType::class + ) { + $this->standardTypes = [ + Type::INT => $this->objectOrLazyInitialize($intType), + Type::FLOAT => $this->objectOrLazyInitialize($floatType), + Type::STRING => $this->objectOrLazyInitialize($stringType), + Type::BOOLEAN => $this->objectOrLazyInitialize($booleanType), + Type::ID => $this->objectOrLazyInitialize($idType), + ]; + + $this->introspection = new Introspection($this); + } + + /** + * @template T of object + * + * @param T|class-string $objectOrClassName + * + * @return T|callable():T + */ + private function objectOrLazyInitialize($objectOrClassName) + { + return is_object($objectOrClassName) ? $objectOrClassName : fn () => new $objectOrClassName(); + } + + /** Returns a singleton instance of itself. */ + public static function instance(): self + { + return self::$instance ??= new self(); + } + + /** + * Register a default instance of the type registry. + * + * When using multiple schemas, you should pass a type registry to the schema constructor instead. + */ + public static function register(self $typeRegistry): void + { + self::$instance = $typeRegistry; + } + + public function standardType(string $name): ScalarType + { + $type = $this->standardTypes[$name]; + if (is_callable($type)) { + return $this->standardTypes[$name] = $type(); + } + + return $type; + } + + /** @throws InvariantViolation */ + public function int(): ScalarType + { + return $this->standardType(Type::INT); + } + + /** @throws InvariantViolation */ + public function float(): ScalarType + { + return $this->standardType(Type::FLOAT); + } + + /** @throws InvariantViolation */ + public function string(): ScalarType + { + return $this->standardType(Type::STRING); + } + + /** @throws InvariantViolation */ + public function boolean(): ScalarType + { + return $this->standardType(Type::BOOLEAN); + } + + /** @throws InvariantViolation */ + public function id(): ScalarType + { + return $this->standardType(Type::ID); + } + + /** + * Returns all builtin scalar types. + * + * @throws InvariantViolation + * + * @return array + */ + public function standardTypes(): array + { + return [ + Type::INT => $this->int(), + Type::FLOAT => $this->float(), + Type::STRING => $this->string(), + Type::BOOLEAN => $this->boolean(), + Type::ID => $this->id(), + ]; + } + + /** + * @throws InvariantViolation + * + * @return array + */ + public function internalDirectives(): array + { + return [ + Directive::INCLUDE_NAME => $this->includeDirective(), + Directive::SKIP_NAME => $this->skipDirective(), + Directive::DEPRECATED_NAME => $this->deprecatedDirective(), + ]; + } + + /** @throws InvariantViolation */ + public function includeDirective(): Directive + { + return $this->directives[Directive::INCLUDE_NAME] ??= new Directive([ + 'name' => Directive::INCLUDE_NAME, + 'description' => 'Directs the executor to include this field or fragment only when the `if` argument is true.', + 'locations' => [ + DirectiveLocation::FIELD, + DirectiveLocation::FRAGMENT_SPREAD, + DirectiveLocation::INLINE_FRAGMENT, + ], + 'args' => [ + Directive::IF_ARGUMENT_NAME => [ + 'type' => Type::nonNull($this->boolean()), + 'description' => 'Included when true.', + ], + ], + ]); + } + + /** @throws InvariantViolation */ + public function skipDirective(): Directive + { + return $this->directives[Directive::SKIP_NAME] ??= new Directive([ + 'name' => Directive::SKIP_NAME, + 'description' => 'Directs the executor to skip this field or fragment when the `if` argument is true.', + 'locations' => [ + DirectiveLocation::FIELD, + DirectiveLocation::FRAGMENT_SPREAD, + DirectiveLocation::INLINE_FRAGMENT, + ], + 'args' => [ + Directive::IF_ARGUMENT_NAME => [ + 'type' => Type::nonNull($this->boolean()), + 'description' => 'Skipped when true.', + ], + ], + ]); + } + + /** @throws InvariantViolation */ + public function deprecatedDirective(): Directive + { + return $this->directives[Directive::DEPRECATED_NAME] ??= new Directive([ + 'name' => Directive::DEPRECATED_NAME, + 'description' => 'Marks an element of a GraphQL schema as no longer supported.', + 'locations' => [ + DirectiveLocation::FIELD_DEFINITION, + DirectiveLocation::ENUM_VALUE, + DirectiveLocation::ARGUMENT_DEFINITION, + DirectiveLocation::INPUT_FIELD_DEFINITION, + ], + 'args' => [ + Directive::REASON_ARGUMENT_NAME => [ + 'type' => $this->string(), + 'description' => 'Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax, as specified by [CommonMark](https://commonmark.org/).', + 'defaultValue' => Directive::DEFAULT_DEPRECATION_REASON, + ], + ], + ]); + } + + public function introspection(): Introspection + { + return $this->introspection; + } +} diff --git a/src/Type/Registry/StandardTypeRegistry.php b/src/Type/Registry/StandardTypeRegistry.php new file mode 100644 index 000000000..9f29dda70 --- /dev/null +++ b/src/Type/Registry/StandardTypeRegistry.php @@ -0,0 +1,64 @@ + $type + * + * @throws InvariantViolation + */ + public function standardType(string $type): ScalarType; + + /** + * Returns all builtin scalar types. + * + * @throws InvariantViolation + * + * @return array + */ + public function standardTypes(): array; + + /** @throws InvariantViolation */ + public function deprecatedDirective(): Directive; + + /** @throws InvariantViolation */ + public function includeDirective(): Directive; + + /** @throws InvariantViolation */ + public function skipDirective(): Directive; + + /** + * @throws InvariantViolation + * + * @return array + */ + public function internalDirectives(): array; + + public function introspection(): Introspection; +} diff --git a/src/Type/Schema.php b/src/Type/Schema.php index 31920e5eb..1bf6907fa 100644 --- a/src/Type/Schema.php +++ b/src/Type/Schema.php @@ -16,6 +16,8 @@ use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; +use GraphQL\Type\Registry\DefaultStandardTypeRegistry; +use GraphQL\Type\Registry\StandardTypeRegistry; use GraphQL\Utils\InterfaceImplementations; use GraphQL\Utils\TypeInfo; use GraphQL\Utils\Utils; @@ -74,6 +76,8 @@ class Schema /** @var array */ public array $extensionASTNodes = []; + public StandardTypeRegistry $typeRegistry; + /** * @param SchemaConfig|array $config * @@ -99,6 +103,8 @@ public function __construct($config) $this->extensionASTNodes = $config->extensionASTNodes; $this->config = $config; + + $this->typeRegistry = $config->typeRegistry ?? DefaultStandardTypeRegistry::instance(); } /** @@ -161,7 +167,7 @@ public function getTypeMap(): array TypeInfo::extractTypesFromDirectives($directive, $allReferencedTypes); } } - TypeInfo::extractTypes(Introspection::_schema(), $allReferencedTypes); + TypeInfo::extractTypes($this->typeRegistry->introspection()->_schema(), $allReferencedTypes); $this->resolvedTypes = $allReferencedTypes; $this->fullyLoaded = true; @@ -181,7 +187,7 @@ public function getTypeMap(): array */ public function getDirectives(): array { - return $this->config->directives ?? GraphQL::getStandardDirectives(); + return $this->config->directives ?? $this->typeRegistry->internalDirectives(); } /** @param mixed $typeLoaderReturn could be anything */ @@ -290,14 +296,13 @@ public function getType(string $name): ?Type return $this->resolvedTypes[$name]; } - $introspectionTypes = Introspection::getTypes(); + $introspectionTypes = $this->typeRegistry->introspection()->getTypes(); if (isset($introspectionTypes[$name])) { return $introspectionTypes[$name]; } - $standardTypes = Type::getStandardTypes(); - if (isset($standardTypes[$name])) { - return $standardTypes[$name]; + if (in_array($name, Type::STANDARD_TYPE_NAMES, true)) { + return $this->typeRegistry->standardType($name); } $type = $this->loadType($name); @@ -508,9 +513,9 @@ public function assertValid(): void throw new InvariantViolation(\implode("\n\n", $this->validationErrors)); } - $internalTypes = Type::getStandardTypes() + Introspection::getTypes(); + $internalTypes = [...Type::STANDARD_TYPE_NAMES, ...Introspection::TYPE_NAMES]; foreach ($this->getTypeMap() as $name => $type) { - if (isset($internalTypes[$name])) { + if (\in_array($name, $internalTypes, true)) { continue; } diff --git a/src/Type/SchemaConfig.php b/src/Type/SchemaConfig.php index e533a048e..cd1a6be5e 100644 --- a/src/Type/SchemaConfig.php +++ b/src/Type/SchemaConfig.php @@ -9,6 +9,7 @@ use GraphQL\Type\Definition\NamedType; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; +use GraphQL\Type\Registry\StandardTypeRegistry; /** * Configuration options for schema construction. @@ -39,6 +40,7 @@ * assumeValid?: bool|null, * astNode?: SchemaDefinitionNode|null, * extensionASTNodes?: array|null, + * typeRegistry?: StandardTypeRegistry|null, * } */ class SchemaConfig @@ -76,6 +78,8 @@ class SchemaConfig /** @var array */ public array $extensionASTNodes = []; + public ?StandardTypeRegistry $typeRegistry = null; + /** * Converts an array of options to instance of SchemaConfig * (or just returns empty config when array is not passed). @@ -126,6 +130,10 @@ public static function create(array $options = []): self if (isset($options['extensionASTNodes'])) { $config->setExtensionASTNodes($options['extensionASTNodes']); } + + if (isset($options['typeRegistry'])) { + $config->setTypeRegistry($options['typeRegistry']); + } } return $config; @@ -316,6 +324,18 @@ public function setExtensionASTNodes(array $extensionASTNodes): self return $this; } + public function getTypeRegistry(): ?StandardTypeRegistry + { + return $this->typeRegistry; + } + + public function setTypeRegistry(StandardTypeRegistry $typeRegistry): self + { + $this->typeRegistry = $typeRegistry; + + return $this; + } + /** * @param mixed $maybeLazyObjectType Should be MaybeLazyObjectType * diff --git a/src/Utils/ASTDefinitionBuilder.php b/src/Utils/ASTDefinitionBuilder.php index e913a489b..c629ff0b3 100644 --- a/src/Utils/ASTDefinitionBuilder.php +++ b/src/Utils/ASTDefinitionBuilder.php @@ -42,6 +42,8 @@ use GraphQL\Type\Definition\OutputType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; +use GraphQL\Type\Registry\DefaultStandardTypeRegistry; +use GraphQL\Type\Registry\StandardTypeRegistry; /** * @see FieldDefinition, InputObjectField @@ -78,27 +80,27 @@ class ASTDefinitionBuilder /** @var array> */ private array $typeExtensionsMap; + private StandardTypeRegistry $typeRegistry; + /** * @param array $typeDefinitionsMap * @param array> $typeExtensionsMap * * @phpstan-param ResolveType $resolveType * @phpstan-param TypeConfigDecorator|null $typeConfigDecorator - * - * @throws InvariantViolation */ public function __construct( array $typeDefinitionsMap, array $typeExtensionsMap, callable $resolveType, - callable $typeConfigDecorator = null + callable $typeConfigDecorator = null, + StandardTypeRegistry $typeRegistry = null ) { $this->typeDefinitionsMap = $typeDefinitionsMap; $this->typeExtensionsMap = $typeExtensionsMap; $this->resolveType = $resolveType; $this->typeConfigDecorator = $typeConfigDecorator; - - $this->cache = Type::builtInTypes(); + $this->typeRegistry = $typeRegistry ?? DefaultStandardTypeRegistry::instance(); } /** @throws \Exception */ @@ -249,6 +251,11 @@ public function maybeBuildType(string $name): ?Type */ private function internalBuildType(string $typeName, Node $typeNode = null): Type { + $this->cache ??= \array_merge( + $this->typeRegistry->introspection()->getTypes(), + $this->typeRegistry->standardTypes() + ); + if (isset($this->cache[$typeName])) { return $this->cache[$typeName]; } @@ -398,7 +405,7 @@ public function buildField(FieldDefinitionNode $field): array private function getDeprecationReason(Node $node): ?string { $deprecated = Values::getDirectiveValues( - Directive::deprecatedDirective(), + $this->typeRegistry->deprecatedDirective(), $node ); diff --git a/src/Utils/BuildClientSchema.php b/src/Utils/BuildClientSchema.php index 084408e1e..36769aa92 100644 --- a/src/Utils/BuildClientSchema.php +++ b/src/Utils/BuildClientSchema.php @@ -21,7 +21,8 @@ use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; -use GraphQL\Type\Introspection; +use GraphQL\Type\Registry\DefaultStandardTypeRegistry; +use GraphQL\Type\Registry\StandardTypeRegistry; use GraphQL\Type\Schema; use GraphQL\Type\SchemaConfig; use GraphQL\Type\TypeKind; @@ -31,7 +32,8 @@ * @phpstan-import-type UnnamedInputObjectFieldConfig from InputObjectField * * @phpstan-type Options array{ - * assumeValid?: bool + * assumeValid?: bool, + * typeRegistry?: StandardTypeRegistry, * } * * - assumeValid: @@ -49,7 +51,7 @@ class BuildClientSchema private array $introspection; /** - * @var array + * @var array * * @phpstan-var Options */ @@ -58,9 +60,11 @@ class BuildClientSchema /** @var array */ private array $typeMap = []; + private StandardTypeRegistry $typeRegistry; + /** * @param array $introspectionQuery - * @param array $options + * @param array $options * * @phpstan-param Options $options */ @@ -68,6 +72,7 @@ public function __construct(array $introspectionQuery, array $options = []) { $this->introspection = $introspectionQuery; $this->options = $options; + $this->typeRegistry = $options['typeRegistry'] ?? DefaultStandardTypeRegistry::instance(); } /** @@ -83,7 +88,7 @@ public function __construct(array $introspectionQuery, array $options = []) * the "errors" field of a server response before calling this function. * * @param array $introspectionQuery - * @param array $options + * @param array $options * * @phpstan-param Options $options * @@ -107,8 +112,8 @@ public function buildSchema(): Schema $schemaIntrospection = $this->introspection['__schema']; $builtInTypes = \array_merge( - Type::getStandardTypes(), - Introspection::getTypes() + $this->typeRegistry->standardTypes(), + $this->typeRegistry->introspection()->getTypes() ); foreach ($schemaIntrospection['types'] as $typeIntrospection) { @@ -147,6 +152,7 @@ public function buildSchema(): Schema return new Schema( (new SchemaConfig()) + ->setTypeRegistry($this->typeRegistry) ->setQuery($queryType) ->setMutation($mutationType) ->setSubscription($subscriptionType) diff --git a/src/Utils/BuildSchema.php b/src/Utils/BuildSchema.php index 31d6d9f61..8d29f5370 100644 --- a/src/Utils/BuildSchema.php +++ b/src/Utils/BuildSchema.php @@ -13,8 +13,9 @@ use GraphQL\Language\AST\TypeExtensionNode; use GraphQL\Language\Parser; use GraphQL\Language\Source; -use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\Type; +use GraphQL\Type\Registry\DefaultStandardTypeRegistry; +use GraphQL\Type\Registry\StandardTypeRegistry; use GraphQL\Type\Schema; use GraphQL\Type\SchemaConfig; use GraphQL\Validator\DocumentValidator; @@ -63,6 +64,8 @@ class BuildSchema */ private array $options; + private StandardTypeRegistry $typeRegistry; + /** * @param array $options * @@ -72,11 +75,14 @@ class BuildSchema public function __construct( DocumentNode $ast, callable $typeConfigDecorator = null, - array $options = [] + array $options = [], + StandardTypeRegistry $typeRegistry = null ) { $this->ast = $ast; $this->typeConfigDecorator = $typeConfigDecorator; $this->options = $options; + + $this->typeRegistry = $typeRegistry ?? DefaultStandardTypeRegistry::instance(); } /** @@ -102,13 +108,14 @@ public function __construct( public static function build( $source, callable $typeConfigDecorator = null, - array $options = [] + array $options = [], + StandardTypeRegistry $typeRegistry = null ): Schema { $doc = $source instanceof DocumentNode ? $source : Parser::parse($source); - return self::buildAST($doc, $typeConfigDecorator, $options); + return self::buildAST($doc, $typeConfigDecorator, $options, $typeRegistry); } /** @@ -135,9 +142,10 @@ public static function build( public static function buildAST( DocumentNode $ast, callable $typeConfigDecorator = null, - array $options = [] + array $options = [], + StandardTypeRegistry $typeRegistry = null ): Schema { - return (new self($ast, $typeConfigDecorator, $options))->buildSchema(); + return (new self($ast, $typeConfigDecorator, $options, $typeRegistry))->buildSchema(); } /** @@ -200,7 +208,8 @@ public function buildSchema(): Schema static function (string $typeName): Type { throw self::unknownType($typeName); }, - $this->typeConfigDecorator + $this->typeConfigDecorator, + $this->typeRegistry ); $directives = \array_map( @@ -215,13 +224,13 @@ static function (string $typeName): Type { // If specified directives were not explicitly declared, add them. if (! isset($directivesByName['include'])) { - $directives[] = Directive::includeDirective(); + $directives[] = $this->typeRegistry->includeDirective(); } if (! isset($directivesByName['skip'])) { - $directives[] = Directive::skipDirective(); + $directives[] = $this->typeRegistry->skipDirective(); } if (! isset($directivesByName['deprecated'])) { - $directives[] = Directive::deprecatedDirective(); + $directives[] = $this->typeRegistry->deprecatedDirective(); } // Note: While this could make early assertions to get the correctly @@ -243,6 +252,7 @@ static function (string $typeName): Type { : null) ->setTypeLoader(static fn (string $name): ?Type => $definitionBuilder->maybeBuildType($name)) ->setDirectives($directives) + ->setTypeRegistry($this->typeRegistry) ->setAstNode($schemaDef) ->setTypes(fn (): array => \array_map( static fn (TypeDefinitionNode $def): Type => $definitionBuilder->buildType($def->getName()->value), diff --git a/src/Utils/SchemaExtender.php b/src/Utils/SchemaExtender.php index 4e1e7a27d..41bfd548d 100644 --- a/src/Utils/SchemaExtender.php +++ b/src/Utils/SchemaExtender.php @@ -557,14 +557,7 @@ protected function extendInterfaceType(InterfaceType $type): InterfaceType protected function isSpecifiedScalarType(Type $type): bool { - return $type instanceof NamedType - && ( - $type->name === Type::STRING - || $type->name === Type::INT - || $type->name === Type::FLOAT - || $type->name === Type::BOOLEAN - || $type->name === Type::ID - ); + return $type instanceof NamedType && in_array($type->name, Type::STANDARD_TYPE_NAMES, true); } /** diff --git a/src/Utils/SchemaPrinter.php b/src/Utils/SchemaPrinter.php index c97be04ac..5fbf4d89c 100644 --- a/src/Utils/SchemaPrinter.php +++ b/src/Utils/SchemaPrinter.php @@ -20,6 +20,7 @@ use GraphQL\Type\Definition\NamedType; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ScalarType; +use GraphQL\Type\Definition\StringType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Introspection; @@ -443,7 +444,7 @@ protected static function printDeprecated($deprecation): string return ' @deprecated'; } - $reasonAST = AST::astFromValue($reason, Type::string()); + $reasonAST = AST::astFromValue($reason, new StringType()); assert($reasonAST instanceof StringValueNode); $reasonASTString = Printer::doPrint($reasonAST); diff --git a/src/Utils/TypeInfo.php b/src/Utils/TypeInfo.php index 4a323db5f..466e571c7 100644 --- a/src/Utils/TypeInfo.php +++ b/src/Utils/TypeInfo.php @@ -32,7 +32,6 @@ use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\WrappingType; -use GraphQL\Type\Introspection; use GraphQL\Type\Schema; class TypeInfo @@ -329,17 +328,17 @@ public function getParentType(): ?CompositeType private static function getFieldDefinition(Schema $schema, Type $parentType, FieldNode $fieldNode): ?FieldDefinition { $name = $fieldNode->name->value; - $schemaMeta = Introspection::schemaMetaFieldDef(); + $schemaMeta = $schema->typeRegistry->introspection()->schemaMetaFieldDef(); if ($name === $schemaMeta->name && $schema->getQueryType() === $parentType) { return $schemaMeta; } - $typeMeta = Introspection::typeMetaFieldDef(); + $typeMeta = $schema->typeRegistry->introspection()->typeMetaFieldDef(); if ($name === $typeMeta->name && $schema->getQueryType() === $parentType) { return $typeMeta; } - $typeNameMeta = Introspection::typeNameMetaFieldDef(); + $typeNameMeta = $schema->typeRegistry->introspection()->typeNameMetaFieldDef(); if ($name === $typeNameMeta->name && $parentType instanceof CompositeType) { return $typeNameMeta; } diff --git a/src/Validator/Rules/KnownArgumentNamesOnDirectives.php b/src/Validator/Rules/KnownArgumentNamesOnDirectives.php index d20f65f3f..7b3b75468 100644 --- a/src/Validator/Rules/KnownArgumentNamesOnDirectives.php +++ b/src/Validator/Rules/KnownArgumentNamesOnDirectives.php @@ -10,7 +10,7 @@ use GraphQL\Language\Visitor; use GraphQL\Language\VisitorOperation; use GraphQL\Type\Definition\Argument; -use GraphQL\Type\Definition\Directive; +use GraphQL\Type\Registry\DefaultStandardTypeRegistry; use GraphQL\Utils\Utils; use GraphQL\Validator\QueryValidationContext; use GraphQL\Validator\SDLValidationContext; @@ -62,7 +62,7 @@ public function getASTVisitor(ValidationContext $context): array $schema = $context->getSchema(); $definedDirectives = $schema !== null ? $schema->getDirectives() - : Directive::getInternalDirectives(); + : DefaultStandardTypeRegistry::instance()->internalDirectives(); foreach ($definedDirectives as $directive) { $directiveArgs[$directive->name] = \array_map( diff --git a/src/Validator/Rules/KnownDirectives.php b/src/Validator/Rules/KnownDirectives.php index 8e692763b..bc9f0ffa2 100644 --- a/src/Validator/Rules/KnownDirectives.php +++ b/src/Validator/Rules/KnownDirectives.php @@ -34,7 +34,7 @@ use GraphQL\Language\AST\VariableDefinitionNode; use GraphQL\Language\DirectiveLocation; use GraphQL\Language\Visitor; -use GraphQL\Type\Definition\Directive; +use GraphQL\Type\Registry\DefaultStandardTypeRegistry; use GraphQL\Validator\QueryValidationContext; use GraphQL\Validator\SDLValidationContext; use GraphQL\Validator\ValidationContext; @@ -66,7 +66,7 @@ public function getASTVisitor(ValidationContext $context): array $locationsMap = []; $schema = $context->getSchema(); $definedDirectives = $schema === null - ? Directive::getInternalDirectives() + ? DefaultStandardTypeRegistry::instance()->internalDirectives() : $schema->getDirectives(); foreach ($definedDirectives as $directive) { diff --git a/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php b/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php index 601645541..8b8ec4d4b 100644 --- a/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php +++ b/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php @@ -11,7 +11,7 @@ use GraphQL\Language\Printer; use GraphQL\Language\Visitor; use GraphQL\Type\Definition\Argument; -use GraphQL\Type\Definition\Directive; +use GraphQL\Type\Registry\DefaultStandardTypeRegistry; use GraphQL\Validator\QueryValidationContext; use GraphQL\Validator\SDLValidationContext; use GraphQL\Validator\ValidationContext; @@ -57,7 +57,7 @@ public function getASTVisitor(ValidationContext $context): array $requiredArgsMap = []; $schema = $context->getSchema(); $definedDirectives = $schema === null - ? Directive::getInternalDirectives() + ? DefaultStandardTypeRegistry::instance()->internalDirectives() : $schema->getDirectives(); foreach ($definedDirectives as $directive) { diff --git a/src/Validator/Rules/QueryComplexity.php b/src/Validator/Rules/QueryComplexity.php index 812482f8f..973698228 100644 --- a/src/Validator/Rules/QueryComplexity.php +++ b/src/Validator/Rules/QueryComplexity.php @@ -181,7 +181,7 @@ protected function directiveExcludesField(FieldNode $node): bool if ($directiveNode->name->value === Directive::INCLUDE_NAME) { $includeArguments = Values::getArgumentValues( - Directive::includeDirective(), + $this->context->getSchema()->typeRegistry->includeDirective(), $directiveNode, $variableValues ); @@ -192,7 +192,7 @@ protected function directiveExcludesField(FieldNode $node): bool if ($directiveNode->name->value === Directive::SKIP_NAME) { $skipArguments = Values::getArgumentValues( - Directive::skipDirective(), + $this->context->getSchema()->typeRegistry->skipDirective(), $directiveNode, $variableValues ); diff --git a/src/Validator/Rules/QuerySecurityRule.php b/src/Validator/Rules/QuerySecurityRule.php index cef9a827c..f960ad938 100644 --- a/src/Validator/Rules/QuerySecurityRule.php +++ b/src/Validator/Rules/QuerySecurityRule.php @@ -12,7 +12,6 @@ use GraphQL\Type\Definition\FieldDefinition; use GraphQL\Type\Definition\HasFieldsType; use GraphQL\Type\Definition\Type; -use GraphQL\Type\Introspection; use GraphQL\Utils\AST; use GraphQL\Validator\QueryValidationContext; @@ -116,9 +115,9 @@ protected function collectFieldASTsAndDefs( $fieldDef = null; if ($parentType instanceof HasFieldsType) { - $schemaMetaFieldDef = Introspection::schemaMetaFieldDef(); - $typeMetaFieldDef = Introspection::typeMetaFieldDef(); - $typeNameMetaFieldDef = Introspection::typeNameMetaFieldDef(); + $schemaMetaFieldDef = $context->getSchema()->typeRegistry->introspection()->schemaMetaFieldDef(); + $typeMetaFieldDef = $context->getSchema()->typeRegistry->introspection()->typeMetaFieldDef(); + $typeNameMetaFieldDef = $context->getSchema()->typeRegistry->introspection()->typeNameMetaFieldDef(); $queryType = $context->getSchema()->getQueryType(); diff --git a/src/Validator/Rules/UniqueDirectivesPerLocation.php b/src/Validator/Rules/UniqueDirectivesPerLocation.php index 8cbc028d5..b6095d8c0 100644 --- a/src/Validator/Rules/UniqueDirectivesPerLocation.php +++ b/src/Validator/Rules/UniqueDirectivesPerLocation.php @@ -7,7 +7,7 @@ use GraphQL\Language\AST\DirectiveDefinitionNode; use GraphQL\Language\AST\Node; use GraphQL\Language\Visitor; -use GraphQL\Type\Definition\Directive; +use GraphQL\Type\Registry\DefaultStandardTypeRegistry; use GraphQL\Validator\QueryValidationContext; use GraphQL\Validator\SDLValidationContext; use GraphQL\Validator\ValidationContext; @@ -47,7 +47,7 @@ public function getASTVisitor(ValidationContext $context): array $schema = $context->getSchema(); $definedDirectives = $schema !== null ? $schema->getDirectives() - : Directive::getInternalDirectives(); + : DefaultStandardTypeRegistry::instance()->internalDirectives(); foreach ($definedDirectives as $directive) { if (! $directive->isRepeatable) { $uniqueDirectiveMap[$directive->name] = true; diff --git a/tests/CustomIntType.php b/tests/CustomIntType.php new file mode 100644 index 000000000..cd6026555 --- /dev/null +++ b/tests/CustomIntType.php @@ -0,0 +1,7 @@ +registry = new DefaultStandardTypeRegistry(); + } + + /** @throws InvariantViolation */ + public function testInitializeWithStandardTypes(): void + { + self::assertInstanceOf(IntType::class, $this->registry->int()); + self::assertInstanceOf(FloatType::class, $this->registry->float()); + self::assertInstanceOf(BooleanType::class, $this->registry->boolean()); + self::assertInstanceOf(StringType::class, $this->registry->string()); + self::assertInstanceOf(IDType::class, $this->registry->id()); + } + + /** @throws InvariantViolation */ + public function testReturnSameStandardTypeInstance(): void + { + self::assertSame($this->registry->int(), $this->registry->int()); + self::assertSame($this->registry->float(), $this->registry->float()); + self::assertSame($this->registry->boolean(), $this->registry->boolean()); + self::assertSame($this->registry->string(), $this->registry->string()); + self::assertSame($this->registry->id(), $this->registry->id()); + } + + /** @throws InvariantViolation */ + public function testAllowOverridingStandardType(): void + { + $this->registry = new DefaultStandardTypeRegistry(CustomIntType::class); + + self::assertInstanceOf(CustomIntType::class, $this->registry->int()); + self::assertInstanceOf(FloatType::class, $this->registry->float()); + self::assertInstanceOf(BooleanType::class, $this->registry->boolean()); + self::assertInstanceOf(StringType::class, $this->registry->string()); + self::assertInstanceOf(IDType::class, $this->registry->id()); + } +} diff --git a/tests/MultipleSchemaTest.php b/tests/MultipleSchemaTest.php new file mode 100644 index 000000000..d1701da02 --- /dev/null +++ b/tests/MultipleSchemaTest.php @@ -0,0 +1,53 @@ +createSchema(); + $result1 = GraphQL::executeQuery($schema1, '{ count }'); + self::assertSame(['data' => ['count' => 1]], $result1->toArray()); + + $schema2 = $this->createSchema(); + $result2 = GraphQL::executeQuery($schema2, '{ count }'); + self::assertSame(['data' => ['count' => 1]], $result2->toArray()); + + self::assertNotSame($schema1->getType('Int'), $schema2->getType('Int')); + } + + /** @throws InvariantViolation */ + private function createSchema(): Schema + { + $typeRegistry = new DefaultStandardTypeRegistry( + CustomIntType::class + ); + + $query = new ObjectType([ + 'name' => 'Query', + 'fields' => [ + 'count' => [ + 'type' => Type::nonNull($typeRegistry->int()), + 'resolve' => fn () => 1, + ], + ], + ]); + + $config = SchemaConfig::create([ + 'typeRegistry' => $typeRegistry, + 'query' => $query, + ]); + + return new Schema($config); + } +} diff --git a/tests/Type/StandardTypesTest.php b/tests/Type/StandardTypesTest.php index 500a30e67..0c468179f 100644 --- a/tests/Type/StandardTypesTest.php +++ b/tests/Type/StandardTypesTest.php @@ -5,31 +5,24 @@ use GraphQL\Error\InvariantViolation; use GraphQL\Type\Definition\CustomScalarType; use GraphQL\Type\Definition\ObjectType; -use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; +use GraphQL\Type\Registry\DefaultStandardTypeRegistry; use PHPUnit\Framework\TestCase; final class StandardTypesTest extends TestCase { - /** @var array */ - private static array $originalStandardTypes; - - public static function setUpBeforeClass(): void - { - self::$originalStandardTypes = Type::getStandardTypes(); - } - public function tearDown(): void { parent::tearDown(); - Type::overrideStandardTypes(self::$originalStandardTypes); + + // Create a new instance of DefaultStandardTypeRegistry to reset the standard types + DefaultStandardTypeRegistry::register(new DefaultStandardTypeRegistry()); } public function testAllowsOverridingStandardTypes(): void { $originalTypes = Type::getStandardTypes(); self::assertCount(5, $originalTypes); - self::assertSame(self::$originalStandardTypes, $originalTypes); $newBooleanType = self::createCustomScalarType(Type::BOOLEAN); $newFloatType = self::createCustomScalarType(Type::FLOAT); @@ -65,7 +58,6 @@ public function testPreservesOriginalStandardTypes(): void { $originalTypes = Type::getStandardTypes(); self::assertCount(5, $originalTypes); - self::assertSame(self::$originalStandardTypes, $originalTypes); $newIDType = self::createCustomScalarType(Type::ID); $newStringType = self::createCustomScalarType(Type::STRING); diff --git a/tests/Utils/BreakingChangesFinderTest.php b/tests/Utils/BreakingChangesFinderTest.php index d46df5761..19583ff14 100644 --- a/tests/Utils/BreakingChangesFinderTest.php +++ b/tests/Utils/BreakingChangesFinderTest.php @@ -10,6 +10,7 @@ use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; +use GraphQL\Type\Registry\DefaultStandardTypeRegistry; use GraphQL\Type\Schema; use GraphQL\Utils\BreakingChangesFinder; use PHPUnit\Framework\TestCase; @@ -1161,7 +1162,7 @@ public function testShouldDetectAllBreakingChanges(): void ], ]); - $directiveThatIsRemoved = Directive::skipDirective(); + $directiveThatIsRemoved = DefaultStandardTypeRegistry::instance()->skipDirective(); $directiveThatRemovesArgOld = new Directive([ 'name' => 'DirectiveThatRemovesArg', 'locations' => [DirectiveLocation::FIELD_DEFINITION], @@ -1303,14 +1304,14 @@ public function testShouldDetectAllBreakingChanges(): void public function testShouldDetectIfADirectiveWasExplicitlyRemoved(): void { $oldSchema = new Schema([ - 'directives' => [Directive::skipDirective(), Directive::includeDirective()], + 'directives' => [DefaultStandardTypeRegistry::instance()->skipDirective(), DefaultStandardTypeRegistry::instance()->includeDirective()], ]); $newSchema = new Schema([ - 'directives' => [Directive::skipDirective()], + 'directives' => [DefaultStandardTypeRegistry::instance()->skipDirective()], ]); - $includeDirective = Directive::includeDirective(); + $includeDirective = DefaultStandardTypeRegistry::instance()->includeDirective(); self::assertEquals( [ @@ -1329,10 +1330,10 @@ public function testShouldDetectIfADirectiveWasImplicitlyRemoved(): void $oldSchema = new Schema([]); $newSchema = new Schema([ - 'directives' => [Directive::skipDirective(), Directive::includeDirective()], + 'directives' => [DefaultStandardTypeRegistry::instance()->skipDirective(), DefaultStandardTypeRegistry::instance()->includeDirective()], ]); - $deprecatedDirective = Directive::deprecatedDirective(); + $deprecatedDirective = DefaultStandardTypeRegistry::instance()->deprecatedDirective(); self::assertEquals( [ diff --git a/tests/Utils/BuildSchemaTest.php b/tests/Utils/BuildSchemaTest.php index 1f87b5dc7..dbe8a3db7 100644 --- a/tests/Utils/BuildSchemaTest.php +++ b/tests/Utils/BuildSchemaTest.php @@ -35,6 +35,7 @@ use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Introspection; +use GraphQL\Type\Registry\DefaultStandardTypeRegistry; use GraphQL\Type\Schema; use GraphQL\Utils\BuildSchema; use GraphQL\Utils\SchemaPrinter; @@ -267,16 +268,17 @@ enum Color { /** @see it('Maintains @include, @skip & @specifiedBy') */ public function testMaintainsIncludeSkipAndSpecifiedBy(): void { - $schema = BuildSchema::buildAST(Parser::parse('type Query')); + $typeRegistry = new DefaultStandardTypeRegistry(); + $schema = BuildSchema::buildAST(Parser::parse('type Query'), null, [], $typeRegistry); // TODO switch to 4 when adding @specifiedBy - see https://github.com/webonyx/graphql-php/issues/1140 self::assertCount(3, $schema->getDirectives()); - self::assertSame(Directive::skipDirective(), $schema->getDirective('skip')); - self::assertSame(Directive::includeDirective(), $schema->getDirective('include')); - self::assertSame(Directive::deprecatedDirective(), $schema->getDirective('deprecated')); + self::assertSame($typeRegistry->skipDirective(), $schema->getDirective('skip')); + self::assertSame($typeRegistry->includeDirective(), $schema->getDirective('include')); + self::assertSame($typeRegistry->deprecatedDirective(), $schema->getDirective('deprecated')); self::markTestIncomplete('See https://github.com/webonyx/graphql-php/issues/1140'); - self::assertSame(Directive::specifiedByDirective(), $schema->getDirective('specifiedBy')); + self::assertSame($typeRegistry->specifiedByDirective(), $schema->getDirective('specifiedBy')); } /** @see it('Overriding directives excludes specified') */ @@ -290,12 +292,12 @@ public function testOverridingDirectivesExcludesSpecified(): void ')); self::assertCount(4, $schema->getDirectives()); - self::assertNotEquals(Directive::skipDirective(), $schema->getDirective('skip')); - self::assertNotEquals(Directive::includeDirective(), $schema->getDirective('include')); - self::assertNotEquals(Directive::deprecatedDirective(), $schema->getDirective('deprecated')); + self::assertNotEquals(DefaultStandardTypeRegistry::instance()->skipDirective(), $schema->getDirective('skip')); + self::assertNotEquals(DefaultStandardTypeRegistry::instance()->includeDirective(), $schema->getDirective('include')); + self::assertNotEquals(DefaultStandardTypeRegistry::instance()->deprecatedDirective(), $schema->getDirective('deprecated')); self::markTestIncomplete('See https://github.com/webonyx/graphql-php/issues/1140'); - self::assertNotEquals(Directive::specifiedByDirective(), $schema->getDirective('specifiedBy')); + self::assertNotEquals(DefaultStandardTypeRegistry::instance()->specifiedByDirective(), $schema->getDirective('specifiedBy')); } /** @see it('Adding directives maintains @include, @skip & @specifiedBy') */ @@ -1219,7 +1221,7 @@ public function testDoNotOverrideStandardTypes(): void '); self::assertSame(Type::id(), $schema->getType('ID')); - self::assertSame(Introspection::_schema(), $schema->getType('__Schema')); + self::assertSame(DefaultStandardTypeRegistry::instance()->introspection()->_schema(), $schema->getType('__Schema')); } /** @see it('Allows to reference introspection types') */ @@ -1236,7 +1238,7 @@ public function testAllowsToReferenceIntrospectionTypes(): void $type = $queryType->getField('introspectionField')->getType(); self::assertInstanceOf(ObjectType::class, $type); self::assertSame('__EnumValue', $type->name); - self::assertSame(Introspection::_enumValue(), $schema->getType('__EnumValue')); + self::assertSame(DefaultStandardTypeRegistry::instance()->introspection()->_enumValue(), $schema->getType('__EnumValue')); } /** @see it('Rejects invalid SDL') */ diff --git a/tests/Validator/ValidatorTestCase.php b/tests/Validator/ValidatorTestCase.php index 78ca2da5c..8b69720c2 100644 --- a/tests/Validator/ValidatorTestCase.php +++ b/tests/Validator/ValidatorTestCase.php @@ -18,6 +18,7 @@ use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; +use GraphQL\Type\Registry\DefaultStandardTypeRegistry; use GraphQL\Type\Schema; use GraphQL\Validator\DocumentValidator; use GraphQL\Validator\Rules\ValidationRule; @@ -57,6 +58,8 @@ protected function expectValid(Schema $schema, array $rules, string $queryString /** @throws InvariantViolation */ public static function getTestSchema(): Schema { + $typeRegistry = DefaultStandardTypeRegistry::instance(); + $Being = new InterfaceType([ 'name' => 'Being', 'fields' => [ @@ -364,9 +367,9 @@ public static function getTestSchema(): Schema 'query' => $queryRoot, 'subscription' => $subscriptionRoot, 'directives' => [ - Directive::includeDirective(), - Directive::skipDirective(), - Directive::deprecatedDirective(), + $typeRegistry->includeDirective(), + $typeRegistry->skipDirective(), + $typeRegistry->deprecatedDirective(), new Directive([ 'name' => 'directive', 'locations' => [DirectiveLocation::FIELD, DirectiveLocation::FRAGMENT_DEFINITION], From 287e1e9c8daa78257bd89a9965c4962a96d45f3b Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Mon, 14 Aug 2023 15:27:38 +0200 Subject: [PATCH 2/9] Apply feedback --- src/Type/Registry/DefaultStandardTypeRegistry.php | 2 +- src/Utils/SchemaExtender.php | 3 ++- src/Validator/Rules/QuerySecurityRule.php | 7 ++++--- tests/DefaultStandardTypeRegistryTest.php | 3 --- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Type/Registry/DefaultStandardTypeRegistry.php b/src/Type/Registry/DefaultStandardTypeRegistry.php index 706e9cc87..5dbdf2344 100644 --- a/src/Type/Registry/DefaultStandardTypeRegistry.php +++ b/src/Type/Registry/DefaultStandardTypeRegistry.php @@ -74,7 +74,7 @@ public function __construct( * * @return T|callable():T */ - private function objectOrLazyInitialize($objectOrClassName) + protected function objectOrLazyInitialize($objectOrClassName) { return is_object($objectOrClassName) ? $objectOrClassName : fn () => new $objectOrClassName(); } diff --git a/src/Utils/SchemaExtender.php b/src/Utils/SchemaExtender.php index 41bfd548d..90344de0f 100644 --- a/src/Utils/SchemaExtender.php +++ b/src/Utils/SchemaExtender.php @@ -557,7 +557,8 @@ protected function extendInterfaceType(InterfaceType $type): InterfaceType protected function isSpecifiedScalarType(Type $type): bool { - return $type instanceof NamedType && in_array($type->name, Type::STANDARD_TYPE_NAMES, true); + return $type instanceof NamedType + && in_array($type->name, Type::STANDARD_TYPE_NAMES, true); } /** diff --git a/src/Validator/Rules/QuerySecurityRule.php b/src/Validator/Rules/QuerySecurityRule.php index f960ad938..3ee13d8c1 100644 --- a/src/Validator/Rules/QuerySecurityRule.php +++ b/src/Validator/Rules/QuerySecurityRule.php @@ -115,9 +115,10 @@ protected function collectFieldASTsAndDefs( $fieldDef = null; if ($parentType instanceof HasFieldsType) { - $schemaMetaFieldDef = $context->getSchema()->typeRegistry->introspection()->schemaMetaFieldDef(); - $typeMetaFieldDef = $context->getSchema()->typeRegistry->introspection()->typeMetaFieldDef(); - $typeNameMetaFieldDef = $context->getSchema()->typeRegistry->introspection()->typeNameMetaFieldDef(); + $introspection = $context->getSchema()->typeRegistry->introspection(); + $schemaMetaFieldDef = $introspection->schemaMetaFieldDef(); + $typeMetaFieldDef = $introspection->typeMetaFieldDef(); + $typeNameMetaFieldDef = $introspection->typeNameMetaFieldDef(); $queryType = $context->getSchema()->getQueryType(); diff --git a/tests/DefaultStandardTypeRegistryTest.php b/tests/DefaultStandardTypeRegistryTest.php index 40a6b263c..46941fc5b 100644 --- a/tests/DefaultStandardTypeRegistryTest.php +++ b/tests/DefaultStandardTypeRegistryTest.php @@ -21,7 +21,6 @@ protected function setUp(): void $this->registry = new DefaultStandardTypeRegistry(); } - /** @throws InvariantViolation */ public function testInitializeWithStandardTypes(): void { self::assertInstanceOf(IntType::class, $this->registry->int()); @@ -31,7 +30,6 @@ public function testInitializeWithStandardTypes(): void self::assertInstanceOf(IDType::class, $this->registry->id()); } - /** @throws InvariantViolation */ public function testReturnSameStandardTypeInstance(): void { self::assertSame($this->registry->int(), $this->registry->int()); @@ -41,7 +39,6 @@ public function testReturnSameStandardTypeInstance(): void self::assertSame($this->registry->id(), $this->registry->id()); } - /** @throws InvariantViolation */ public function testAllowOverridingStandardType(): void { $this->registry = new DefaultStandardTypeRegistry(CustomIntType::class); From 0d3ff243cddfa9135223c675cea96c9eb5170e0c Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Mon, 14 Aug 2023 15:39:14 +0200 Subject: [PATCH 3/9] Extract BuiltInDirectiveRegistry --- .../Registry/BuiltInDirectiveRegistry.php | 25 +++++++++++++++++++ .../Registry/DefaultStandardTypeRegistry.php | 2 +- src/Type/Registry/StandardTypeRegistry.php | 16 ------------ 3 files changed, 26 insertions(+), 17 deletions(-) create mode 100644 src/Type/Registry/BuiltInDirectiveRegistry.php diff --git a/src/Type/Registry/BuiltInDirectiveRegistry.php b/src/Type/Registry/BuiltInDirectiveRegistry.php new file mode 100644 index 000000000..7bcc289df --- /dev/null +++ b/src/Type/Registry/BuiltInDirectiveRegistry.php @@ -0,0 +1,25 @@ + + */ + public function internalDirectives(): array; +} diff --git a/src/Type/Registry/DefaultStandardTypeRegistry.php b/src/Type/Registry/DefaultStandardTypeRegistry.php index 5dbdf2344..60bab47aa 100644 --- a/src/Type/Registry/DefaultStandardTypeRegistry.php +++ b/src/Type/Registry/DefaultStandardTypeRegistry.php @@ -22,7 +22,7 @@ * When a type by class is requested, it will return a callable that will instantiate the type on first call. * On subsequent calls, it will return the same instance immediately. */ -class DefaultStandardTypeRegistry implements StandardTypeRegistry +class DefaultStandardTypeRegistry implements StandardTypeRegistry, BuiltInDirectiveRegistry { private static self $instance; diff --git a/src/Type/Registry/StandardTypeRegistry.php b/src/Type/Registry/StandardTypeRegistry.php index 9f29dda70..63dca05fa 100644 --- a/src/Type/Registry/StandardTypeRegistry.php +++ b/src/Type/Registry/StandardTypeRegistry.php @@ -3,7 +3,6 @@ namespace GraphQL\Type\Registry; use GraphQL\Error\InvariantViolation; -use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Introspection; @@ -44,21 +43,6 @@ public function standardType(string $type): ScalarType; */ public function standardTypes(): array; - /** @throws InvariantViolation */ - public function deprecatedDirective(): Directive; - - /** @throws InvariantViolation */ - public function includeDirective(): Directive; - - /** @throws InvariantViolation */ - public function skipDirective(): Directive; - - /** - * @throws InvariantViolation - * - * @return array - */ - public function internalDirectives(): array; public function introspection(): Introspection; } From fb12480d3d76d861c275d11c07ccc29f4ed4caa7 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Mon, 14 Aug 2023 15:39:25 +0200 Subject: [PATCH 4/9] Deprecate all static getters on Type --- src/Type/Definition/Type.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Type/Definition/Type.php b/src/Type/Definition/Type.php index 390b1719d..0554e9750 100644 --- a/src/Type/Definition/Type.php +++ b/src/Type/Definition/Type.php @@ -32,6 +32,8 @@ abstract class Type implements \JsonSerializable ]; /** + * @deprecated use the `typeRegistry` on the `Schema` instead + * * @api * * @throws InvariantViolation @@ -42,6 +44,8 @@ public static function int(): ScalarType } /** + * @deprecated use the `typeRegistry` on the `Schema` instead + * * @api * * @throws InvariantViolation @@ -52,6 +56,8 @@ public static function float(): ScalarType } /** + * @deprecated use the `typeRegistry` on the `Schema` instead + * * @api * * @throws InvariantViolation @@ -62,6 +68,8 @@ public static function string(): ScalarType } /** + * @deprecated use the `typeRegistry` on the `Schema` instead + * * @api * * @throws InvariantViolation @@ -72,6 +80,8 @@ public static function boolean(): ScalarType } /** + * @deprecated use the `typeRegistry` on the `Schema` instead + * * @api * * @throws InvariantViolation @@ -108,6 +118,8 @@ public static function nonNull($type): NonNull /** * Returns all builtin scalar types. * + * @deprecated use the `typeRegistry` on the `Schema` instead + * * @throws InvariantViolation * * @return array @@ -118,7 +130,7 @@ public static function getStandardTypes(): array } /** - * @deprecated use a custom instance of `DefaultStandardTypeRegistry` on the `Schema` instead + * @deprecated use the `typeRegistry` on the `Schema` instead * * @param array $types * From 149476204f726c5f99d0b3313102498363e7e90d Mon Sep 17 00:00:00 2001 From: ruudk Date: Mon, 14 Aug 2023 13:40:21 +0000 Subject: [PATCH 5/9] Apply php-cs-fixer changes --- src/Type/Registry/StandardTypeRegistry.php | 1 - tests/DefaultStandardTypeRegistryTest.php | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Type/Registry/StandardTypeRegistry.php b/src/Type/Registry/StandardTypeRegistry.php index 63dca05fa..6243d3faa 100644 --- a/src/Type/Registry/StandardTypeRegistry.php +++ b/src/Type/Registry/StandardTypeRegistry.php @@ -43,6 +43,5 @@ public function standardType(string $type): ScalarType; */ public function standardTypes(): array; - public function introspection(): Introspection; } diff --git a/tests/DefaultStandardTypeRegistryTest.php b/tests/DefaultStandardTypeRegistryTest.php index 46941fc5b..4b7c51b98 100644 --- a/tests/DefaultStandardTypeRegistryTest.php +++ b/tests/DefaultStandardTypeRegistryTest.php @@ -2,7 +2,6 @@ namespace GraphQL\Tests; -use GraphQL\Error\InvariantViolation; use GraphQL\Type\Definition\BooleanType; use GraphQL\Type\Definition\FloatType; use GraphQL\Type\Definition\IDType; From 193a8eff3bfd40624b5023eb1092a56d97f277b2 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Tue, 15 Aug 2023 08:54:31 +0200 Subject: [PATCH 6/9] Fix type --- src/Type/Schema.php | 4 +++- src/Type/SchemaConfig.php | 16 ++++++++++++---- src/Utils/ASTDefinitionBuilder.php | 7 +++++-- src/Utils/BuildClientSchema.php | 6 ++++-- src/Utils/BuildSchema.php | 13 +++++++++---- 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/Type/Schema.php b/src/Type/Schema.php index 1bf6907fa..b8eea9aca 100644 --- a/src/Type/Schema.php +++ b/src/Type/Schema.php @@ -16,6 +16,7 @@ use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; +use GraphQL\Type\Registry\BuiltInDirectiveRegistry; use GraphQL\Type\Registry\DefaultStandardTypeRegistry; use GraphQL\Type\Registry\StandardTypeRegistry; use GraphQL\Utils\InterfaceImplementations; @@ -76,7 +77,8 @@ class Schema /** @var array */ public array $extensionASTNodes = []; - public StandardTypeRegistry $typeRegistry; + /** @var StandardTypeRegistry&BuiltInDirectiveRegistry */ + public $typeRegistry; /** * @param SchemaConfig|array $config diff --git a/src/Type/SchemaConfig.php b/src/Type/SchemaConfig.php index cd1a6be5e..bfcf7939c 100644 --- a/src/Type/SchemaConfig.php +++ b/src/Type/SchemaConfig.php @@ -9,6 +9,7 @@ use GraphQL\Type\Definition\NamedType; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; +use GraphQL\Type\Registry\BuiltInDirectiveRegistry; use GraphQL\Type\Registry\StandardTypeRegistry; /** @@ -40,7 +41,7 @@ * assumeValid?: bool|null, * astNode?: SchemaDefinitionNode|null, * extensionASTNodes?: array|null, - * typeRegistry?: StandardTypeRegistry|null, + * typeRegistry?: null|(StandardTypeRegistry&BuiltInDirectiveRegistry), * } */ class SchemaConfig @@ -78,7 +79,8 @@ class SchemaConfig /** @var array */ public array $extensionASTNodes = []; - public ?StandardTypeRegistry $typeRegistry = null; + /** @var (StandardTypeRegistry&BuiltInDirectiveRegistry)|null */ + public $typeRegistry; /** * Converts an array of options to instance of SchemaConfig @@ -324,12 +326,18 @@ public function setExtensionASTNodes(array $extensionASTNodes): self return $this; } - public function getTypeRegistry(): ?StandardTypeRegistry + /** @return (StandardTypeRegistry&BuiltInDirectiveRegistry)|null */ + public function getTypeRegistry() { return $this->typeRegistry; } - public function setTypeRegistry(StandardTypeRegistry $typeRegistry): self + /** + * @param (StandardTypeRegistry&BuiltInDirectiveRegistry)|null $typeRegistry + * + * @return $this + */ + public function setTypeRegistry($typeRegistry): self { $this->typeRegistry = $typeRegistry; diff --git a/src/Utils/ASTDefinitionBuilder.php b/src/Utils/ASTDefinitionBuilder.php index c629ff0b3..a5c2a788f 100644 --- a/src/Utils/ASTDefinitionBuilder.php +++ b/src/Utils/ASTDefinitionBuilder.php @@ -42,6 +42,7 @@ use GraphQL\Type\Definition\OutputType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; +use GraphQL\Type\Registry\BuiltInDirectiveRegistry; use GraphQL\Type\Registry\DefaultStandardTypeRegistry; use GraphQL\Type\Registry\StandardTypeRegistry; @@ -80,11 +81,13 @@ class ASTDefinitionBuilder /** @var array> */ private array $typeExtensionsMap; - private StandardTypeRegistry $typeRegistry; + /** @var StandardTypeRegistry&BuiltInDirectiveRegistry */ + private $typeRegistry; /** * @param array $typeDefinitionsMap * @param array> $typeExtensionsMap + * @param (StandardTypeRegistry&BuiltInDirectiveRegistry)|null $typeRegistry * * @phpstan-param ResolveType $resolveType * @phpstan-param TypeConfigDecorator|null $typeConfigDecorator @@ -94,7 +97,7 @@ public function __construct( array $typeExtensionsMap, callable $resolveType, callable $typeConfigDecorator = null, - StandardTypeRegistry $typeRegistry = null + $typeRegistry = null ) { $this->typeDefinitionsMap = $typeDefinitionsMap; $this->typeExtensionsMap = $typeExtensionsMap; diff --git a/src/Utils/BuildClientSchema.php b/src/Utils/BuildClientSchema.php index 36769aa92..fdbbdb18f 100644 --- a/src/Utils/BuildClientSchema.php +++ b/src/Utils/BuildClientSchema.php @@ -21,6 +21,7 @@ use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; +use GraphQL\Type\Registry\BuiltInDirectiveRegistry; use GraphQL\Type\Registry\DefaultStandardTypeRegistry; use GraphQL\Type\Registry\StandardTypeRegistry; use GraphQL\Type\Schema; @@ -33,7 +34,7 @@ * * @phpstan-type Options array{ * assumeValid?: bool, - * typeRegistry?: StandardTypeRegistry, + * typeRegistry?: null|(StandardTypeRegistry&BuiltInDirectiveRegistry), * } * * - assumeValid: @@ -60,7 +61,8 @@ class BuildClientSchema /** @var array */ private array $typeMap = []; - private StandardTypeRegistry $typeRegistry; + /** @var StandardTypeRegistry&BuiltInDirectiveRegistry */ + private $typeRegistry; /** * @param array $introspectionQuery diff --git a/src/Utils/BuildSchema.php b/src/Utils/BuildSchema.php index 8d29f5370..98372dfa9 100644 --- a/src/Utils/BuildSchema.php +++ b/src/Utils/BuildSchema.php @@ -14,6 +14,7 @@ use GraphQL\Language\Parser; use GraphQL\Language\Source; use GraphQL\Type\Definition\Type; +use GraphQL\Type\Registry\BuiltInDirectiveRegistry; use GraphQL\Type\Registry\DefaultStandardTypeRegistry; use GraphQL\Type\Registry\StandardTypeRegistry; use GraphQL\Type\Schema; @@ -64,10 +65,12 @@ class BuildSchema */ private array $options; - private StandardTypeRegistry $typeRegistry; + /** @var StandardTypeRegistry&BuiltInDirectiveRegistry */ + private $typeRegistry; /** * @param array $options + * @param (StandardTypeRegistry&BuiltInDirectiveRegistry)|null $typeRegistry * * @phpstan-param TypeConfigDecorator|null $typeConfigDecorator * @phpstan-param BuildSchemaOptions $options @@ -76,7 +79,7 @@ public function __construct( DocumentNode $ast, callable $typeConfigDecorator = null, array $options = [], - StandardTypeRegistry $typeRegistry = null + $typeRegistry = null ) { $this->ast = $ast; $this->typeConfigDecorator = $typeConfigDecorator; @@ -94,6 +97,7 @@ public function __construct( * @phpstan-param TypeConfigDecorator|null $typeConfigDecorator * * @param array $options + * @param (StandardTypeRegistry&BuiltInDirectiveRegistry)|null $typeRegistry * * @phpstan-param BuildSchemaOptions $options * @@ -109,7 +113,7 @@ public static function build( $source, callable $typeConfigDecorator = null, array $options = [], - StandardTypeRegistry $typeRegistry = null + $typeRegistry = null ): Schema { $doc = $source instanceof DocumentNode ? $source @@ -129,6 +133,7 @@ public static function build( * @phpstan-param TypeConfigDecorator|null $typeConfigDecorator * * @param array $options + * @param (StandardTypeRegistry&BuiltInDirectiveRegistry)|null $typeRegistry * * @phpstan-param BuildSchemaOptions $options * @@ -143,7 +148,7 @@ public static function buildAST( DocumentNode $ast, callable $typeConfigDecorator = null, array $options = [], - StandardTypeRegistry $typeRegistry = null + $typeRegistry = null ): Schema { return (new self($ast, $typeConfigDecorator, $options, $typeRegistry))->buildSchema(); } From 12252acbac6afb91757e805587dde51c6377d744 Mon Sep 17 00:00:00 2001 From: ruudk Date: Tue, 15 Aug 2023 06:58:58 +0000 Subject: [PATCH 7/9] Prettify docs --- docs/class-reference.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/class-reference.md b/docs/class-reference.md index fc82356f0..0dd11960c 100644 --- a/docs/class-reference.md +++ b/docs/class-reference.md @@ -172,6 +172,8 @@ Registry of standard GraphQL types and base class for all other types. ```php /** + * @deprecated use the `typeRegistry` on the `Schema` instead + * * @api * * @throws InvariantViolation @@ -181,6 +183,8 @@ static function int(): GraphQL\Type\Definition\ScalarType ```php /** + * @deprecated use the `typeRegistry` on the `Schema` instead + * * @api * * @throws InvariantViolation @@ -190,6 +194,8 @@ static function float(): GraphQL\Type\Definition\ScalarType ```php /** + * @deprecated use the `typeRegistry` on the `Schema` instead + * * @api * * @throws InvariantViolation @@ -199,6 +205,8 @@ static function string(): GraphQL\Type\Definition\ScalarType ```php /** + * @deprecated use the `typeRegistry` on the `Schema` instead + * * @api * * @throws InvariantViolation @@ -208,6 +216,8 @@ static function boolean(): GraphQL\Type\Definition\ScalarType ```php /** + * @deprecated use the `typeRegistry` on the `Schema` instead + * * @api * * @throws InvariantViolation @@ -548,7 +558,7 @@ typeLoader?: TypeLoader|null, assumeValid?: bool|null, astNode?: SchemaDefinitionNode|null, extensionASTNodes?: array|null, -typeRegistry?: StandardTypeRegistry|null, +typeRegistry?: null|(StandardTypeRegistry&BuiltInDirectiveRegistry), } ### GraphQL\Type\SchemaConfig Methods @@ -2419,6 +2429,7 @@ assumeValidSDL?: bool * @phpstan-param TypeConfigDecorator|null $typeConfigDecorator * * @param array $options + * @param (StandardTypeRegistry&BuiltInDirectiveRegistry)|null $typeRegistry * * @phpstan-param BuildSchemaOptions $options * @@ -2434,7 +2445,7 @@ static function build( $source, ?callable $typeConfigDecorator = null, array $options = [], - ?GraphQL\Type\Registry\StandardTypeRegistry $typeRegistry = null + $typeRegistry = null ): GraphQL\Type\Schema ``` @@ -2450,6 +2461,7 @@ static function build( * @phpstan-param TypeConfigDecorator|null $typeConfigDecorator * * @param array $options + * @param (StandardTypeRegistry&BuiltInDirectiveRegistry)|null $typeRegistry * * @phpstan-param BuildSchemaOptions $options * @@ -2464,7 +2476,7 @@ static function buildAST( GraphQL\Language\AST\DocumentNode $ast, ?callable $typeConfigDecorator = null, array $options = [], - ?GraphQL\Type\Registry\StandardTypeRegistry $typeRegistry = null + $typeRegistry = null ): GraphQL\Type\Schema ``` From 432cc04b2a78d4e7694fd1036339ed822cde73a4 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Tue, 15 Aug 2023 09:26:40 +0200 Subject: [PATCH 8/9] Remove Introspection from StandardTypeRegistry We now store this separately. --- src/Executor/ReferenceExecutor.php | 6 ++--- .../Registry/DefaultStandardTypeRegistry.php | 10 --------- src/Type/Registry/StandardTypeRegistry.php | 3 --- src/Type/Schema.php | 7 ++++-- src/Type/SchemaConfig.php | 15 +++++++++++++ src/Utils/ASTDefinitionBuilder.php | 9 ++++++-- src/Utils/BuildClientSchema.php | 18 ++++++++++----- src/Utils/BuildSchema.php | 22 +++++++++++++------ src/Utils/SchemaExtender.php | 6 ++++- src/Utils/TypeInfo.php | 6 ++--- src/Validator/Rules/QuerySecurityRule.php | 2 +- tests/Utils/BuildClientSchemaTest.php | 8 +++++-- tests/Utils/BuildSchemaTest.php | 10 ++++++--- 13 files changed, 79 insertions(+), 43 deletions(-) diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 2ae88294e..847ef9860 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -661,17 +661,17 @@ protected function resolveField(ObjectType $parentType, $rootValue, \ArrayObject protected function getFieldDef(Schema $schema, ObjectType $parentType, string $fieldName): ?FieldDefinition { $queryType = $schema->getQueryType(); - $schemaMeta = $schema->typeRegistry->introspection()->schemaMetaFieldDef(); + $schemaMeta = $schema->introspection->schemaMetaFieldDef(); if ($fieldName === $schemaMeta->name && $queryType === $parentType) { return $schemaMeta; } - $typeMeta = $schema->typeRegistry->introspection()->typeMetaFieldDef(); + $typeMeta = $schema->introspection->typeMetaFieldDef(); if ($fieldName === $typeMeta->name && $queryType === $parentType) { return $typeMeta; } - $typeNameMeta = $schema->typeRegistry->introspection()->typeNameMetaFieldDef(); + $typeNameMeta = $schema->introspection->typeNameMetaFieldDef(); if ($fieldName === $typeNameMeta->name) { return $typeNameMeta; } diff --git a/src/Type/Registry/DefaultStandardTypeRegistry.php b/src/Type/Registry/DefaultStandardTypeRegistry.php index 60bab47aa..27e7c6fda 100644 --- a/src/Type/Registry/DefaultStandardTypeRegistry.php +++ b/src/Type/Registry/DefaultStandardTypeRegistry.php @@ -12,7 +12,6 @@ use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\StringType; use GraphQL\Type\Definition\Type; -use GraphQL\Type\Introspection; /** * A default implementation of the type registry. @@ -40,8 +39,6 @@ class DefaultStandardTypeRegistry implements StandardTypeRegistry, BuiltInDirect /** @var array */ protected array $directives = []; - protected Introspection $introspection; - /** * @param ScalarType|class-string $intType * @param ScalarType|class-string $floatType @@ -63,8 +60,6 @@ public function __construct( Type::BOOLEAN => $this->objectOrLazyInitialize($booleanType), Type::ID => $this->objectOrLazyInitialize($idType), ]; - - $this->introspection = new Introspection($this); } /** @@ -228,9 +223,4 @@ public function deprecatedDirective(): Directive ], ]); } - - public function introspection(): Introspection - { - return $this->introspection; - } } diff --git a/src/Type/Registry/StandardTypeRegistry.php b/src/Type/Registry/StandardTypeRegistry.php index 6243d3faa..fdbf4e88f 100644 --- a/src/Type/Registry/StandardTypeRegistry.php +++ b/src/Type/Registry/StandardTypeRegistry.php @@ -5,7 +5,6 @@ use GraphQL\Error\InvariantViolation; use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; -use GraphQL\Type\Introspection; /** * A registry that returns standard types. @@ -42,6 +41,4 @@ public function standardType(string $type): ScalarType; * @return array */ public function standardTypes(): array; - - public function introspection(): Introspection; } diff --git a/src/Type/Schema.php b/src/Type/Schema.php index b8eea9aca..e5390d98f 100644 --- a/src/Type/Schema.php +++ b/src/Type/Schema.php @@ -80,6 +80,8 @@ class Schema /** @var StandardTypeRegistry&BuiltInDirectiveRegistry */ public $typeRegistry; + public Introspection $introspection; + /** * @param SchemaConfig|array $config * @@ -107,6 +109,7 @@ public function __construct($config) $this->config = $config; $this->typeRegistry = $config->typeRegistry ?? DefaultStandardTypeRegistry::instance(); + $this->introspection = $config->introspection ?? new Introspection($this->typeRegistry); } /** @@ -169,7 +172,7 @@ public function getTypeMap(): array TypeInfo::extractTypesFromDirectives($directive, $allReferencedTypes); } } - TypeInfo::extractTypes($this->typeRegistry->introspection()->_schema(), $allReferencedTypes); + TypeInfo::extractTypes($this->introspection->_schema(), $allReferencedTypes); $this->resolvedTypes = $allReferencedTypes; $this->fullyLoaded = true; @@ -298,7 +301,7 @@ public function getType(string $name): ?Type return $this->resolvedTypes[$name]; } - $introspectionTypes = $this->typeRegistry->introspection()->getTypes(); + $introspectionTypes = $this->introspection->getTypes(); if (isset($introspectionTypes[$name])) { return $introspectionTypes[$name]; } diff --git a/src/Type/SchemaConfig.php b/src/Type/SchemaConfig.php index bfcf7939c..0a8904cb5 100644 --- a/src/Type/SchemaConfig.php +++ b/src/Type/SchemaConfig.php @@ -82,6 +82,8 @@ class SchemaConfig /** @var (StandardTypeRegistry&BuiltInDirectiveRegistry)|null */ public $typeRegistry; + public ?Introspection $introspection = null; + /** * Converts an array of options to instance of SchemaConfig * (or just returns empty config when array is not passed). @@ -344,6 +346,19 @@ public function setTypeRegistry($typeRegistry): self return $this; } + public function getIntrospection(): ?Introspection + { + return $this->introspection; + } + + /** @return $this */ + public function setIntrospection(?Introspection $introspection): self + { + $this->introspection = $introspection; + + return $this; + } + /** * @param mixed $maybeLazyObjectType Should be MaybeLazyObjectType * diff --git a/src/Utils/ASTDefinitionBuilder.php b/src/Utils/ASTDefinitionBuilder.php index a5c2a788f..9ca77f1ed 100644 --- a/src/Utils/ASTDefinitionBuilder.php +++ b/src/Utils/ASTDefinitionBuilder.php @@ -42,6 +42,7 @@ use GraphQL\Type\Definition\OutputType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; +use GraphQL\Type\Introspection; use GraphQL\Type\Registry\BuiltInDirectiveRegistry; use GraphQL\Type\Registry\DefaultStandardTypeRegistry; use GraphQL\Type\Registry\StandardTypeRegistry; @@ -84,6 +85,8 @@ class ASTDefinitionBuilder /** @var StandardTypeRegistry&BuiltInDirectiveRegistry */ private $typeRegistry; + private Introspection $introspection; + /** * @param array $typeDefinitionsMap * @param array> $typeExtensionsMap @@ -97,13 +100,15 @@ public function __construct( array $typeExtensionsMap, callable $resolveType, callable $typeConfigDecorator = null, - $typeRegistry = null + $typeRegistry = null, + Introspection $introspection = null, ) { $this->typeDefinitionsMap = $typeDefinitionsMap; $this->typeExtensionsMap = $typeExtensionsMap; $this->resolveType = $resolveType; $this->typeConfigDecorator = $typeConfigDecorator; $this->typeRegistry = $typeRegistry ?? DefaultStandardTypeRegistry::instance(); + $this->introspection = $introspection ?? new Introspection($this->typeRegistry); } /** @throws \Exception */ @@ -255,7 +260,7 @@ public function maybeBuildType(string $name): ?Type private function internalBuildType(string $typeName, Node $typeNode = null): Type { $this->cache ??= \array_merge( - $this->typeRegistry->introspection()->getTypes(), + $this->introspection->getTypes(), $this->typeRegistry->standardTypes() ); diff --git a/src/Utils/BuildClientSchema.php b/src/Utils/BuildClientSchema.php index fdbbdb18f..b92864f25 100644 --- a/src/Utils/BuildClientSchema.php +++ b/src/Utils/BuildClientSchema.php @@ -21,6 +21,7 @@ use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; +use GraphQL\Type\Introspection; use GraphQL\Type\Registry\BuiltInDirectiveRegistry; use GraphQL\Type\Registry\DefaultStandardTypeRegistry; use GraphQL\Type\Registry\StandardTypeRegistry; @@ -35,6 +36,7 @@ * @phpstan-type Options array{ * assumeValid?: bool, * typeRegistry?: null|(StandardTypeRegistry&BuiltInDirectiveRegistry), + * introspection?: null|Introspection, * } * * - assumeValid: @@ -49,7 +51,7 @@ class BuildClientSchema { /** @var array */ - private array $introspection; + private array $introspectionQuery; /** * @var array @@ -64,6 +66,8 @@ class BuildClientSchema /** @var StandardTypeRegistry&BuiltInDirectiveRegistry */ private $typeRegistry; + private Introspection $introspection; + /** * @param array $introspectionQuery * @param array $options @@ -72,9 +76,10 @@ class BuildClientSchema */ public function __construct(array $introspectionQuery, array $options = []) { - $this->introspection = $introspectionQuery; + $this->introspectionQuery = $introspectionQuery; $this->options = $options; $this->typeRegistry = $options['typeRegistry'] ?? DefaultStandardTypeRegistry::instance(); + $this->introspection = $options['introspection'] ?? new Introspection($this->typeRegistry); } /** @@ -106,16 +111,16 @@ public static function build(array $introspectionQuery, array $options = []): Sc /** @throws InvariantViolation */ public function buildSchema(): Schema { - if (! \array_key_exists('__schema', $this->introspection)) { - $missingSchemaIntrospection = Utils::printSafeJson($this->introspection); + if (! \array_key_exists('__schema', $this->introspectionQuery)) { + $missingSchemaIntrospection = Utils::printSafeJson($this->introspectionQuery); throw new InvariantViolation("Invalid or incomplete introspection result. Ensure that you are passing \"data\" property of introspection response and no \"errors\" was returned alongside: {$missingSchemaIntrospection}."); } - $schemaIntrospection = $this->introspection['__schema']; + $schemaIntrospection = $this->introspectionQuery['__schema']; $builtInTypes = \array_merge( $this->typeRegistry->standardTypes(), - $this->typeRegistry->introspection()->getTypes() + $this->introspection->getTypes() ); foreach ($schemaIntrospection['types'] as $typeIntrospection) { @@ -155,6 +160,7 @@ public function buildSchema(): Schema return new Schema( (new SchemaConfig()) ->setTypeRegistry($this->typeRegistry) + ->setIntrospection($this->introspection) ->setQuery($queryType) ->setMutation($mutationType) ->setSubscription($subscriptionType) diff --git a/src/Utils/BuildSchema.php b/src/Utils/BuildSchema.php index 98372dfa9..94ad29af6 100644 --- a/src/Utils/BuildSchema.php +++ b/src/Utils/BuildSchema.php @@ -14,6 +14,7 @@ use GraphQL\Language\Parser; use GraphQL\Language\Source; use GraphQL\Type\Definition\Type; +use GraphQL\Type\Introspection; use GraphQL\Type\Registry\BuiltInDirectiveRegistry; use GraphQL\Type\Registry\DefaultStandardTypeRegistry; use GraphQL\Type\Registry\StandardTypeRegistry; @@ -68,6 +69,8 @@ class BuildSchema /** @var StandardTypeRegistry&BuiltInDirectiveRegistry */ private $typeRegistry; + private ?Introspection $introspection = null; + /** * @param array $options * @param (StandardTypeRegistry&BuiltInDirectiveRegistry)|null $typeRegistry @@ -79,13 +82,14 @@ public function __construct( DocumentNode $ast, callable $typeConfigDecorator = null, array $options = [], - $typeRegistry = null + $typeRegistry = null, + Introspection $introspection = null ) { $this->ast = $ast; $this->typeConfigDecorator = $typeConfigDecorator; $this->options = $options; - $this->typeRegistry = $typeRegistry ?? DefaultStandardTypeRegistry::instance(); + $this->introspection = $introspection; } /** @@ -113,13 +117,14 @@ public static function build( $source, callable $typeConfigDecorator = null, array $options = [], - $typeRegistry = null + $typeRegistry = null, + Introspection $introspection = null ): Schema { $doc = $source instanceof DocumentNode ? $source : Parser::parse($source); - return self::buildAST($doc, $typeConfigDecorator, $options, $typeRegistry); + return self::buildAST($doc, $typeConfigDecorator, $options, $typeRegistry, $introspection); } /** @@ -148,9 +153,10 @@ public static function buildAST( DocumentNode $ast, callable $typeConfigDecorator = null, array $options = [], - $typeRegistry = null + $typeRegistry = null, + Introspection $introspection = null ): Schema { - return (new self($ast, $typeConfigDecorator, $options, $typeRegistry))->buildSchema(); + return (new self($ast, $typeConfigDecorator, $options, $typeRegistry, $introspection))->buildSchema(); } /** @@ -214,7 +220,8 @@ static function (string $typeName): Type { throw self::unknownType($typeName); }, $this->typeConfigDecorator, - $this->typeRegistry + $this->typeRegistry, + $this->introspection ); $directives = \array_map( @@ -258,6 +265,7 @@ static function (string $typeName): Type { ->setTypeLoader(static fn (string $name): ?Type => $definitionBuilder->maybeBuildType($name)) ->setDirectives($directives) ->setTypeRegistry($this->typeRegistry) + ->setIntrospection($this->introspection) ->setAstNode($schemaDef) ->setTypes(fn (): array => \array_map( static fn (TypeDefinitionNode $def): Type => $definitionBuilder->buildType($def->getName()->value), diff --git a/src/Utils/SchemaExtender.php b/src/Utils/SchemaExtender.php index 90344de0f..105951bdf 100644 --- a/src/Utils/SchemaExtender.php +++ b/src/Utils/SchemaExtender.php @@ -146,7 +146,9 @@ function (string $typeName) use ($schema): Type { return $this->extendNamedType($existingType); }, - $typeConfigDecorator + $typeConfigDecorator, + $schema->typeRegistry, + $schema->introspection ); $this->extendTypeCache = []; @@ -196,6 +198,8 @@ function (string $typeName) use ($schema): Type { ->setDirectives($this->getMergedDirectives($schema, $directiveDefinitions)) ->setAstNode($schema->astNode ?? $schemaDef) ->setExtensionASTNodes($schemaExtensionASTNodes) + ->setTypeRegistry($schema->typeRegistry) + ->setIntrospection($schema->introspection) ); } diff --git a/src/Utils/TypeInfo.php b/src/Utils/TypeInfo.php index 466e571c7..c171ad5a8 100644 --- a/src/Utils/TypeInfo.php +++ b/src/Utils/TypeInfo.php @@ -328,17 +328,17 @@ public function getParentType(): ?CompositeType private static function getFieldDefinition(Schema $schema, Type $parentType, FieldNode $fieldNode): ?FieldDefinition { $name = $fieldNode->name->value; - $schemaMeta = $schema->typeRegistry->introspection()->schemaMetaFieldDef(); + $schemaMeta = $schema->introspection->schemaMetaFieldDef(); if ($name === $schemaMeta->name && $schema->getQueryType() === $parentType) { return $schemaMeta; } - $typeMeta = $schema->typeRegistry->introspection()->typeMetaFieldDef(); + $typeMeta = $schema->introspection->typeMetaFieldDef(); if ($name === $typeMeta->name && $schema->getQueryType() === $parentType) { return $typeMeta; } - $typeNameMeta = $schema->typeRegistry->introspection()->typeNameMetaFieldDef(); + $typeNameMeta = $schema->introspection->typeNameMetaFieldDef(); if ($name === $typeNameMeta->name && $parentType instanceof CompositeType) { return $typeNameMeta; } diff --git a/src/Validator/Rules/QuerySecurityRule.php b/src/Validator/Rules/QuerySecurityRule.php index 3ee13d8c1..c43b16260 100644 --- a/src/Validator/Rules/QuerySecurityRule.php +++ b/src/Validator/Rules/QuerySecurityRule.php @@ -115,7 +115,7 @@ protected function collectFieldASTsAndDefs( $fieldDef = null; if ($parentType instanceof HasFieldsType) { - $introspection = $context->getSchema()->typeRegistry->introspection(); + $introspection = $context->getSchema()->introspection; $schemaMetaFieldDef = $introspection->schemaMetaFieldDef(); $typeMetaFieldDef = $introspection->typeMetaFieldDef(); $typeNameMetaFieldDef = $introspection->typeNameMetaFieldDef(); diff --git a/tests/Utils/BuildClientSchemaTest.php b/tests/Utils/BuildClientSchemaTest.php index ab8710aa6..f22e68c1d 100644 --- a/tests/Utils/BuildClientSchemaTest.php +++ b/tests/Utils/BuildClientSchemaTest.php @@ -9,6 +9,7 @@ use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Introspection; +use GraphQL\Type\Registry\DefaultStandardTypeRegistry; use GraphQL\Type\Schema; use GraphQL\Utils\BuildClientSchema; use GraphQL\Utils\BuildSchema; @@ -28,10 +29,13 @@ protected static function assertCycleIntrospection(string $sdl): void { $options = ['directiveIsRepeatable' => true]; - $serverSchema = BuildSchema::build($sdl); + $typeRegistry = new DefaultStandardTypeRegistry(); + $introspection = new Introspection($typeRegistry); + + $serverSchema = BuildSchema::build($sdl, null, [], $typeRegistry, $introspection); $initialIntrospection = Introspection::fromSchema($serverSchema, $options); - $clientSchema = BuildClientSchema::build($initialIntrospection); + $clientSchema = BuildClientSchema::build($initialIntrospection, ['typeRegistry' => $typeRegistry, 'introspection' => $introspection]); $secondIntrospection = Introspection::fromSchema($clientSchema, $options); self::assertSame($initialIntrospection, $secondIntrospection); diff --git a/tests/Utils/BuildSchemaTest.php b/tests/Utils/BuildSchemaTest.php index dbe8a3db7..992c50db4 100644 --- a/tests/Utils/BuildSchemaTest.php +++ b/tests/Utils/BuildSchemaTest.php @@ -142,7 +142,11 @@ public function testMatchOrderOfDefaultTypesAndDirectives(): void { $schema = new Schema([]); $sdlSchema = BuildSchema::buildAST( - new DocumentNode(['definitions' => new NodeList([])]) + new DocumentNode(['definitions' => new NodeList([])]), + null, + [], + $schema->typeRegistry, + $schema->introspection ); self::assertEquals(array_values($schema->getDirectives()), $sdlSchema->getDirectives()); @@ -1221,7 +1225,7 @@ public function testDoNotOverrideStandardTypes(): void '); self::assertSame(Type::id(), $schema->getType('ID')); - self::assertSame(DefaultStandardTypeRegistry::instance()->introspection()->_schema(), $schema->getType('__Schema')); + self::assertSame($schema->introspection->_schema(), $schema->getType('__Schema')); } /** @see it('Allows to reference introspection types') */ @@ -1238,7 +1242,7 @@ public function testAllowsToReferenceIntrospectionTypes(): void $type = $queryType->getField('introspectionField')->getType(); self::assertInstanceOf(ObjectType::class, $type); self::assertSame('__EnumValue', $type->name); - self::assertSame(DefaultStandardTypeRegistry::instance()->introspection()->_enumValue(), $schema->getType('__EnumValue')); + self::assertSame($schema->introspection->_enumValue(), $schema->getType('__EnumValue')); } /** @see it('Rejects invalid SDL') */ From 9d7d6e03af8a6366e3a5c38a2ae0c0c54e967cf3 Mon Sep 17 00:00:00 2001 From: ruudk Date: Tue, 15 Aug 2023 07:28:01 +0000 Subject: [PATCH 9/9] Prettify docs --- docs/class-reference.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/class-reference.md b/docs/class-reference.md index 0dd11960c..8e6ed5b28 100644 --- a/docs/class-reference.md +++ b/docs/class-reference.md @@ -2445,7 +2445,8 @@ static function build( $source, ?callable $typeConfigDecorator = null, array $options = [], - $typeRegistry = null + $typeRegistry = null, + ?GraphQL\Type\Introspection $introspection = null ): GraphQL\Type\Schema ``` @@ -2476,7 +2477,8 @@ static function buildAST( GraphQL\Language\AST\DocumentNode $ast, ?callable $typeConfigDecorator = null, array $options = [], - $typeRegistry = null + $typeRegistry = null, + ?GraphQL\Type\Introspection $introspection = null ): GraphQL\Type\Schema ```