diff --git a/docs/class-reference.md b/docs/class-reference.md index 1577d3c35..8e6ed5b28 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,6 +558,7 @@ typeLoader?: TypeLoader|null, assumeValid?: bool|null, astNode?: SchemaDefinitionNode|null, extensionASTNodes?: array|null, +typeRegistry?: null|(StandardTypeRegistry&BuiltInDirectiveRegistry), } ### GraphQL\Type\SchemaConfig Methods @@ -2418,6 +2429,7 @@ assumeValidSDL?: bool * @phpstan-param TypeConfigDecorator|null $typeConfigDecorator * * @param array $options + * @param (StandardTypeRegistry&BuiltInDirectiveRegistry)|null $typeRegistry * * @phpstan-param BuildSchemaOptions $options * @@ -2429,7 +2441,13 @@ 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 = [], + $typeRegistry = null, + ?GraphQL\Type\Introspection $introspection = null +): GraphQL\Type\Schema ``` ```php @@ -2444,6 +2462,7 @@ static function build($source, ?callable $typeConfigDecorator = null, array $opt * @phpstan-param TypeConfigDecorator|null $typeConfigDecorator * * @param array $options + * @param (StandardTypeRegistry&BuiltInDirectiveRegistry)|null $typeRegistry * * @phpstan-param BuildSchemaOptions $options * @@ -2457,7 +2476,9 @@ static function build($source, ?callable $typeConfigDecorator = null, array $opt static function buildAST( GraphQL\Language\AST\DocumentNode $ast, ?callable $typeConfigDecorator = null, - array $options = [] + array $options = [], + $typeRegistry = null, + ?GraphQL\Type\Introspection $introspection = null ): GraphQL\Type\Schema ``` diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index fa3c63379..847ef9860 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->introspection->schemaMetaFieldDef(); + if ($fieldName === $schemaMeta->name && $queryType === $parentType) { + return $schemaMeta; } - if ($fieldName === $typeMetaFieldDef->name && $queryType === $parentType) { - return $typeMetaFieldDef; + $typeMeta = $schema->introspection->typeMetaFieldDef(); + if ($fieldName === $typeMeta->name && $queryType === $parentType) { + return $typeMeta; } - if ($fieldName === $typeNameMetaFieldDef->name) { - return $typeNameMetaFieldDef; + $typeNameMeta = $schema->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..0554e9750 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,57 +31,64 @@ abstract class Type implements \JsonSerializable ...Introspection::TYPE_NAMES, ]; - /** @var array */ - protected static array $standardTypes; - /** + * @deprecated use the `typeRegistry` on the `Schema` instead + * * @api * * @throws InvariantViolation */ public static function int(): ScalarType { - return static::$standardTypes[self::INT] ??= new IntType(); + return DefaultStandardTypeRegistry::instance()->int(); } /** + * @deprecated use the `typeRegistry` on the `Schema` instead + * * @api * * @throws InvariantViolation */ public static function float(): ScalarType { - return static::$standardTypes[self::FLOAT] ??= new FloatType(); + return DefaultStandardTypeRegistry::instance()->float(); } /** + * @deprecated use the `typeRegistry` on the `Schema` instead + * * @api * * @throws InvariantViolation */ public static function string(): ScalarType { - return static::$standardTypes[self::STRING] ??= new StringType(); + return DefaultStandardTypeRegistry::instance()->string(); } /** + * @deprecated use the `typeRegistry` on the `Schema` instead + * * @api * * @throws InvariantViolation */ public static function boolean(): ScalarType { - return static::$standardTypes[self::BOOLEAN] ??= new BooleanType(); + return DefaultStandardTypeRegistry::instance()->boolean(); } /** + * @deprecated use the `typeRegistry` on the `Schema` instead + * * @api * * @throws InvariantViolation */ public static function id(): ScalarType { - return static::$standardTypes[self::ID] ??= new IDType(); + return DefaultStandardTypeRegistry::instance()->id(); } /** @@ -107,48 +115,31 @@ 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. * + * @deprecated use the `typeRegistry` on the `Schema` instead + * * @throws InvariantViolation * * @return 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 the `typeRegistry` 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 +154,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/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 new file mode 100644 index 000000000..27e7c6fda --- /dev/null +++ b/src/Type/Registry/DefaultStandardTypeRegistry.php @@ -0,0 +1,226 @@ + */ + protected array $directives = []; + + /** + * @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), + ]; + } + + /** + * @template T of object + * + * @param T|class-string $objectOrClassName + * + * @return T|callable():T + */ + protected 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, + ], + ], + ]); + } +} diff --git a/src/Type/Registry/StandardTypeRegistry.php b/src/Type/Registry/StandardTypeRegistry.php new file mode 100644 index 000000000..fdbf4e88f --- /dev/null +++ b/src/Type/Registry/StandardTypeRegistry.php @@ -0,0 +1,44 @@ + $type + * + * @throws InvariantViolation + */ + public function standardType(string $type): ScalarType; + + /** + * Returns all builtin scalar types. + * + * @throws InvariantViolation + * + * @return array + */ + public function standardTypes(): array; +} diff --git a/src/Type/Schema.php b/src/Type/Schema.php index 31920e5eb..e5390d98f 100644 --- a/src/Type/Schema.php +++ b/src/Type/Schema.php @@ -16,6 +16,9 @@ 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; use GraphQL\Utils\TypeInfo; use GraphQL\Utils\Utils; @@ -74,6 +77,11 @@ class Schema /** @var array */ public array $extensionASTNodes = []; + /** @var StandardTypeRegistry&BuiltInDirectiveRegistry */ + public $typeRegistry; + + public Introspection $introspection; + /** * @param SchemaConfig|array $config * @@ -99,6 +107,9 @@ public function __construct($config) $this->extensionASTNodes = $config->extensionASTNodes; $this->config = $config; + + $this->typeRegistry = $config->typeRegistry ?? DefaultStandardTypeRegistry::instance(); + $this->introspection = $config->introspection ?? new Introspection($this->typeRegistry); } /** @@ -161,7 +172,7 @@ public function getTypeMap(): array TypeInfo::extractTypesFromDirectives($directive, $allReferencedTypes); } } - TypeInfo::extractTypes(Introspection::_schema(), $allReferencedTypes); + TypeInfo::extractTypes($this->introspection->_schema(), $allReferencedTypes); $this->resolvedTypes = $allReferencedTypes; $this->fullyLoaded = true; @@ -181,7 +192,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 +301,13 @@ public function getType(string $name): ?Type return $this->resolvedTypes[$name]; } - $introspectionTypes = Introspection::getTypes(); + $introspectionTypes = $this->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 +518,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..0a8904cb5 100644 --- a/src/Type/SchemaConfig.php +++ b/src/Type/SchemaConfig.php @@ -9,6 +9,8 @@ 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; /** * Configuration options for schema construction. @@ -39,6 +41,7 @@ * assumeValid?: bool|null, * astNode?: SchemaDefinitionNode|null, * extensionASTNodes?: array|null, + * typeRegistry?: null|(StandardTypeRegistry&BuiltInDirectiveRegistry), * } */ class SchemaConfig @@ -76,6 +79,11 @@ class SchemaConfig /** @var array */ public array $extensionASTNodes = []; + /** @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). @@ -126,6 +134,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 +328,37 @@ public function setExtensionASTNodes(array $extensionASTNodes): self return $this; } + /** @return (StandardTypeRegistry&BuiltInDirectiveRegistry)|null */ + public function getTypeRegistry() + { + return $this->typeRegistry; + } + + /** + * @param (StandardTypeRegistry&BuiltInDirectiveRegistry)|null $typeRegistry + * + * @return $this + */ + public function setTypeRegistry($typeRegistry): self + { + $this->typeRegistry = $typeRegistry; + + 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 e913a489b..9ca77f1ed 100644 --- a/src/Utils/ASTDefinitionBuilder.php +++ b/src/Utils/ASTDefinitionBuilder.php @@ -42,6 +42,10 @@ 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; /** * @see FieldDefinition, InputObjectField @@ -78,27 +82,33 @@ class ASTDefinitionBuilder /** @var array> */ private array $typeExtensionsMap; + /** @var StandardTypeRegistry&BuiltInDirectiveRegistry */ + private $typeRegistry; + + private Introspection $introspection; + /** * @param array $typeDefinitionsMap * @param array> $typeExtensionsMap + * @param (StandardTypeRegistry&BuiltInDirectiveRegistry)|null $typeRegistry * * @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, + $typeRegistry = null, + Introspection $introspection = null, ) { $this->typeDefinitionsMap = $typeDefinitionsMap; $this->typeExtensionsMap = $typeExtensionsMap; $this->resolveType = $resolveType; $this->typeConfigDecorator = $typeConfigDecorator; - - $this->cache = Type::builtInTypes(); + $this->typeRegistry = $typeRegistry ?? DefaultStandardTypeRegistry::instance(); + $this->introspection = $introspection ?? new Introspection($this->typeRegistry); } /** @throws \Exception */ @@ -249,6 +259,11 @@ public function maybeBuildType(string $name): ?Type */ private function internalBuildType(string $typeName, Node $typeNode = null): Type { + $this->cache ??= \array_merge( + $this->introspection->getTypes(), + $this->typeRegistry->standardTypes() + ); + if (isset($this->cache[$typeName])) { return $this->cache[$typeName]; } @@ -398,7 +413,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..b92864f25 100644 --- a/src/Utils/BuildClientSchema.php +++ b/src/Utils/BuildClientSchema.php @@ -22,6 +22,9 @@ 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; use GraphQL\Type\Schema; use GraphQL\Type\SchemaConfig; use GraphQL\Type\TypeKind; @@ -31,7 +34,9 @@ * @phpstan-import-type UnnamedInputObjectFieldConfig from InputObjectField * * @phpstan-type Options array{ - * assumeValid?: bool + * assumeValid?: bool, + * typeRegistry?: null|(StandardTypeRegistry&BuiltInDirectiveRegistry), + * introspection?: null|Introspection, * } * * - assumeValid: @@ -46,10 +51,10 @@ class BuildClientSchema { /** @var array */ - private array $introspection; + private array $introspectionQuery; /** - * @var array + * @var array * * @phpstan-var Options */ @@ -58,16 +63,23 @@ class BuildClientSchema /** @var array */ private array $typeMap = []; + /** @var StandardTypeRegistry&BuiltInDirectiveRegistry */ + private $typeRegistry; + + private Introspection $introspection; + /** * @param array $introspectionQuery - * @param array $options + * @param array $options * * @phpstan-param Options $options */ 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); } /** @@ -83,7 +95,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 * @@ -99,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( - Type::getStandardTypes(), - Introspection::getTypes() + $this->typeRegistry->standardTypes(), + $this->introspection->getTypes() ); foreach ($schemaIntrospection['types'] as $typeIntrospection) { @@ -147,6 +159,8 @@ 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 31d6d9f61..94ad29af6 100644 --- a/src/Utils/BuildSchema.php +++ b/src/Utils/BuildSchema.php @@ -13,8 +13,11 @@ 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\Introspection; +use GraphQL\Type\Registry\BuiltInDirectiveRegistry; +use GraphQL\Type\Registry\DefaultStandardTypeRegistry; +use GraphQL\Type\Registry\StandardTypeRegistry; use GraphQL\Type\Schema; use GraphQL\Type\SchemaConfig; use GraphQL\Validator\DocumentValidator; @@ -63,8 +66,14 @@ class BuildSchema */ private array $options; + /** @var StandardTypeRegistry&BuiltInDirectiveRegistry */ + private $typeRegistry; + + private ?Introspection $introspection = null; + /** * @param array $options + * @param (StandardTypeRegistry&BuiltInDirectiveRegistry)|null $typeRegistry * * @phpstan-param TypeConfigDecorator|null $typeConfigDecorator * @phpstan-param BuildSchemaOptions $options @@ -72,11 +81,15 @@ class BuildSchema public function __construct( DocumentNode $ast, callable $typeConfigDecorator = null, - array $options = [] + array $options = [], + $typeRegistry = null, + Introspection $introspection = null ) { $this->ast = $ast; $this->typeConfigDecorator = $typeConfigDecorator; $this->options = $options; + $this->typeRegistry = $typeRegistry ?? DefaultStandardTypeRegistry::instance(); + $this->introspection = $introspection; } /** @@ -88,6 +101,7 @@ public function __construct( * @phpstan-param TypeConfigDecorator|null $typeConfigDecorator * * @param array $options + * @param (StandardTypeRegistry&BuiltInDirectiveRegistry)|null $typeRegistry * * @phpstan-param BuildSchemaOptions $options * @@ -102,13 +116,15 @@ public function __construct( public static function build( $source, callable $typeConfigDecorator = null, - array $options = [] + array $options = [], + $typeRegistry = null, + Introspection $introspection = null ): Schema { $doc = $source instanceof DocumentNode ? $source : Parser::parse($source); - return self::buildAST($doc, $typeConfigDecorator, $options); + return self::buildAST($doc, $typeConfigDecorator, $options, $typeRegistry, $introspection); } /** @@ -122,6 +138,7 @@ public static function build( * @phpstan-param TypeConfigDecorator|null $typeConfigDecorator * * @param array $options + * @param (StandardTypeRegistry&BuiltInDirectiveRegistry)|null $typeRegistry * * @phpstan-param BuildSchemaOptions $options * @@ -135,9 +152,11 @@ public static function build( public static function buildAST( DocumentNode $ast, callable $typeConfigDecorator = null, - array $options = [] + array $options = [], + $typeRegistry = null, + Introspection $introspection = null ): Schema { - return (new self($ast, $typeConfigDecorator, $options))->buildSchema(); + return (new self($ast, $typeConfigDecorator, $options, $typeRegistry, $introspection))->buildSchema(); } /** @@ -200,7 +219,9 @@ public function buildSchema(): Schema static function (string $typeName): Type { throw self::unknownType($typeName); }, - $this->typeConfigDecorator + $this->typeConfigDecorator, + $this->typeRegistry, + $this->introspection ); $directives = \array_map( @@ -215,13 +236,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 +264,8 @@ static function (string $typeName): Type { : null) ->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 4e1e7a27d..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) ); } @@ -558,13 +562,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 - ); + && 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..c171ad5a8 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->introspection->schemaMetaFieldDef(); if ($name === $schemaMeta->name && $schema->getQueryType() === $parentType) { return $schemaMeta; } - $typeMeta = Introspection::typeMetaFieldDef(); + $typeMeta = $schema->introspection->typeMetaFieldDef(); if ($name === $typeMeta->name && $schema->getQueryType() === $parentType) { return $typeMeta; } - $typeNameMeta = Introspection::typeNameMetaFieldDef(); + $typeNameMeta = $schema->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..c43b16260 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,10 @@ protected function collectFieldASTsAndDefs( $fieldDef = null; if ($parentType instanceof HasFieldsType) { - $schemaMetaFieldDef = Introspection::schemaMetaFieldDef(); - $typeMetaFieldDef = Introspection::typeMetaFieldDef(); - $typeNameMetaFieldDef = Introspection::typeNameMetaFieldDef(); + $introspection = $context->getSchema()->introspection; + $schemaMetaFieldDef = $introspection->schemaMetaFieldDef(); + $typeMetaFieldDef = $introspection->typeMetaFieldDef(); + $typeNameMetaFieldDef = $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(); + } + + 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()); + } + + 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()); + } + + 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/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 1f87b5dc7..992c50db4 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; @@ -141,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()); @@ -267,16 +272,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 +296,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 +1225,7 @@ public function testDoNotOverrideStandardTypes(): void '); self::assertSame(Type::id(), $schema->getType('ID')); - self::assertSame(Introspection::_schema(), $schema->getType('__Schema')); + self::assertSame($schema->introspection->_schema(), $schema->getType('__Schema')); } /** @see it('Allows to reference introspection types') */ @@ -1236,7 +1242,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($schema->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],