Skip to content

Commit a167afa

Browse files
authored
Reset static caches of instances using standard types when overriding them
1 parent accee09 commit a167afa

File tree

5 files changed

+136
-92
lines changed

5 files changed

+136
-92
lines changed

src/Executor/ReferenceExecutor.php

+19-10
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ class ReferenceExecutor implements ExecutorImplementation
6969
*/
7070
protected \SplObjectStorage $fieldArgsCache;
7171

72+
protected FieldDefinition $schemaMetaFieldDef;
73+
74+
protected FieldDefinition $typeMetaFieldDef;
75+
76+
protected FieldDefinition $typeNameMetaFieldDef;
77+
7278
protected function __construct(ExecutionContext $context)
7379
{
7480
if (! isset(static::$UNDEFINED)) {
@@ -701,23 +707,26 @@ protected function resolveField(
701707
*/
702708
protected function getFieldDef(Schema $schema, ObjectType $parentType, string $fieldName): ?FieldDefinition
703709
{
704-
static $schemaMetaFieldDef, $typeMetaFieldDef, $typeNameMetaFieldDef;
705-
$schemaMetaFieldDef ??= Introspection::schemaMetaFieldDef();
706-
$typeMetaFieldDef ??= Introspection::typeMetaFieldDef();
707-
$typeNameMetaFieldDef ??= Introspection::typeNameMetaFieldDef();
710+
$this->schemaMetaFieldDef ??= Introspection::schemaMetaFieldDef();
711+
$this->typeMetaFieldDef ??= Introspection::typeMetaFieldDef();
712+
$this->typeNameMetaFieldDef ??= Introspection::typeNameMetaFieldDef();
708713

709714
$queryType = $schema->getQueryType();
710715

711-
if ($fieldName === $schemaMetaFieldDef->name && $queryType === $parentType) {
712-
return $schemaMetaFieldDef;
716+
if ($fieldName === $this->schemaMetaFieldDef->name
717+
&& $queryType === $parentType
718+
) {
719+
return $this->schemaMetaFieldDef;
713720
}
714721

715-
if ($fieldName === $typeMetaFieldDef->name && $queryType === $parentType) {
716-
return $typeMetaFieldDef;
722+
if ($fieldName === $this->typeMetaFieldDef->name
723+
&& $queryType === $parentType
724+
) {
725+
return $this->typeMetaFieldDef;
717726
}
718727

719-
if ($fieldName === $typeNameMetaFieldDef->name) {
720-
return $typeNameMetaFieldDef;
728+
if ($fieldName === $this->typeNameMetaFieldDef->name) {
729+
return $this->typeNameMetaFieldDef;
721730
}
722731

723732
return $parentType->findField($fieldName);

src/Type/Definition/Directive.php

+63-64
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ class Directive
3131
/**
3232
* Lazily initialized.
3333
*
34-
* @var array<string, Directive>
34+
* @var array<string, Directive>|null
3535
*/
36-
protected static array $internalDirectives;
36+
protected static ?array $internalDirectives = null;
3737

3838
public string $name;
3939

@@ -75,91 +75,90 @@ public function __construct(array $config)
7575
$this->config = $config;
7676
}
7777

78-
/** @throws InvariantViolation */
79-
public static function includeDirective(): Directive
80-
{
81-
$internal = self::getInternalDirectives();
82-
83-
return $internal['include'];
84-
}
85-
8678
/**
8779
* @throws InvariantViolation
8880
*
8981
* @return array<string, Directive>
9082
*/
9183
public static function getInternalDirectives(): array
9284
{
93-
return self::$internalDirectives ??= [
94-
'include' => new self([
95-
'name' => self::INCLUDE_NAME,
96-
'description' => 'Directs the executor to include this field or fragment only when the `if` argument is true.',
97-
'locations' => [
98-
DirectiveLocation::FIELD,
99-
DirectiveLocation::FRAGMENT_SPREAD,
100-
DirectiveLocation::INLINE_FRAGMENT,
101-
],
102-
'args' => [
103-
self::IF_ARGUMENT_NAME => [
104-
'type' => Type::nonNull(Type::boolean()),
105-
'description' => 'Included when true.',
106-
],
107-
],
108-
]),
109-
'skip' => new self([
110-
'name' => self::SKIP_NAME,
111-
'description' => 'Directs the executor to skip this field or fragment when the `if` argument is true.',
112-
'locations' => [
113-
DirectiveLocation::FIELD,
114-
DirectiveLocation::FRAGMENT_SPREAD,
115-
DirectiveLocation::INLINE_FRAGMENT,
116-
],
117-
'args' => [
118-
self::IF_ARGUMENT_NAME => [
119-
'type' => Type::nonNull(Type::boolean()),
120-
'description' => 'Skipped when true.',
121-
],
122-
],
123-
]),
124-
'deprecated' => new self([
125-
'name' => self::DEPRECATED_NAME,
126-
'description' => 'Marks an element of a GraphQL schema as no longer supported.',
127-
'locations' => [
128-
DirectiveLocation::FIELD_DEFINITION,
129-
DirectiveLocation::ENUM_VALUE,
130-
DirectiveLocation::ARGUMENT_DEFINITION,
131-
DirectiveLocation::INPUT_FIELD_DEFINITION,
132-
],
133-
'args' => [
134-
self::REASON_ARGUMENT_NAME => [
135-
'type' => Type::string(),
136-
'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/).',
137-
'defaultValue' => self::DEFAULT_DEPRECATION_REASON,
138-
],
139-
],
140-
]),
85+
return [
86+
self::INCLUDE_NAME => self::includeDirective(),
87+
self::SKIP_NAME => self::skipDirective(),
88+
self::DEPRECATED_NAME => self::deprecatedDirective(),
14189
];
14290
}
14391

14492
/** @throws InvariantViolation */
145-
public static function skipDirective(): Directive
93+
public static function includeDirective(): Directive
14694
{
147-
$internal = self::getInternalDirectives();
95+
return self::$internalDirectives[self::INCLUDE_NAME] ??= new self([
96+
'name' => self::INCLUDE_NAME,
97+
'description' => 'Directs the executor to include this field or fragment only when the `if` argument is true.',
98+
'locations' => [
99+
DirectiveLocation::FIELD,
100+
DirectiveLocation::FRAGMENT_SPREAD,
101+
DirectiveLocation::INLINE_FRAGMENT,
102+
],
103+
'args' => [
104+
self::IF_ARGUMENT_NAME => [
105+
'type' => Type::nonNull(Type::boolean()),
106+
'description' => 'Included when true.',
107+
],
108+
],
109+
]);
110+
}
148111

149-
return $internal['skip'];
112+
/** @throws InvariantViolation */
113+
public static function skipDirective(): Directive
114+
{
115+
return self::$internalDirectives[self::SKIP_NAME] ??= new self([
116+
'name' => self::SKIP_NAME,
117+
'description' => 'Directs the executor to skip this field or fragment when the `if` argument is true.',
118+
'locations' => [
119+
DirectiveLocation::FIELD,
120+
DirectiveLocation::FRAGMENT_SPREAD,
121+
DirectiveLocation::INLINE_FRAGMENT,
122+
],
123+
'args' => [
124+
self::IF_ARGUMENT_NAME => [
125+
'type' => Type::nonNull(Type::boolean()),
126+
'description' => 'Skipped when true.',
127+
],
128+
],
129+
]);
150130
}
151131

152132
/** @throws InvariantViolation */
153133
public static function deprecatedDirective(): Directive
154134
{
155-
$internal = self::getInternalDirectives();
156-
157-
return $internal['deprecated'];
135+
return self::$internalDirectives[self::DEPRECATED_NAME] ??= new self([
136+
'name' => self::DEPRECATED_NAME,
137+
'description' => 'Marks an element of a GraphQL schema as no longer supported.',
138+
'locations' => [
139+
DirectiveLocation::FIELD_DEFINITION,
140+
DirectiveLocation::ENUM_VALUE,
141+
DirectiveLocation::ARGUMENT_DEFINITION,
142+
DirectiveLocation::INPUT_FIELD_DEFINITION,
143+
],
144+
'args' => [
145+
self::REASON_ARGUMENT_NAME => [
146+
'type' => Type::string(),
147+
'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/).',
148+
'defaultValue' => self::DEFAULT_DEPRECATION_REASON,
149+
],
150+
],
151+
]);
158152
}
159153

160154
/** @throws InvariantViolation */
161155
public static function isSpecifiedDirective(Directive $directive): bool
162156
{
163157
return \array_key_exists($directive->name, self::getInternalDirectives());
164158
}
159+
160+
public static function resetCachedInstances(): void
161+
{
162+
self::$internalDirectives = null;
163+
}
165164
}

src/Type/Definition/Type.php

+11-5
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@ abstract class Type implements \JsonSerializable
3030
...Introspection::TYPE_NAMES,
3131
];
3232

33-
/** @var array<string, ScalarType> */
34-
protected static array $standardTypes;
33+
/** @var array<string, ScalarType>|null */
34+
protected static ?array $standardTypes;
35+
36+
/** @var array<string, Type&NamedType>|null */
37+
protected static ?array $builtInTypes;
3538

3639
/**
3740
* @api
@@ -116,9 +119,7 @@ public static function nonNull($type): NonNull
116119
*/
117120
public static function builtInTypes(): array
118121
{
119-
static $builtInTypes;
120-
121-
return $builtInTypes ??= \array_merge(
122+
return self::$builtInTypes ??= \array_merge(
122123
Introspection::getTypes(),
123124
self::getStandardTypes()
124125
);
@@ -149,6 +150,11 @@ public static function getStandardTypes(): array
149150
*/
150151
public static function overrideStandardTypes(array $types): void
151152
{
153+
// Reset caches that might contain instances of standard types
154+
static::$builtInTypes = null;
155+
Introspection::resetCachedInstances();
156+
Directive::resetCachedInstances();
157+
152158
foreach ($types as $type) {
153159
// @phpstan-ignore-next-line generic type is not enforced by PHP
154160
if (! $type instanceof ScalarType) {

src/Type/Introspection.php

+18-13
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ class Introspection
6868
self::DIRECTIVE_LOCATION_ENUM_NAME,
6969
];
7070

71-
/** @var array<string, mixed> */
72-
private static $map = [];
71+
/** @var array<string, mixed>|null */
72+
protected static ?array $cachedInstances;
7373

7474
/**
7575
* @param IntrospectionOptions $options
@@ -253,7 +253,7 @@ public static function getTypes(): array
253253
/** @throws InvariantViolation */
254254
public static function _schema(): ObjectType
255255
{
256-
return self::$map[self::SCHEMA_OBJECT_NAME] ??= new ObjectType([
256+
return self::$cachedInstances[self::SCHEMA_OBJECT_NAME] ??= new ObjectType([
257257
'name' => self::SCHEMA_OBJECT_NAME,
258258
'isIntrospection' => true,
259259
'description' => 'A GraphQL Schema defines the capabilities of a GraphQL '
@@ -293,7 +293,7 @@ public static function _schema(): ObjectType
293293
/** @throws InvariantViolation */
294294
public static function _type(): ObjectType
295295
{
296-
return self::$map[self::TYPE_OBJECT_NAME] ??= new ObjectType([
296+
return self::$cachedInstances[self::TYPE_OBJECT_NAME] ??= new ObjectType([
297297
'name' => self::TYPE_OBJECT_NAME,
298298
'isIntrospection' => true,
299299
'description' => 'The fundamental unit of any GraphQL Schema is the type. There are '
@@ -444,7 +444,7 @@ public static function _type(): ObjectType
444444
/** @throws InvariantViolation */
445445
public static function _typeKind(): EnumType
446446
{
447-
return self::$map[self::TYPE_KIND_ENUM_NAME] ??= new EnumType([
447+
return self::$cachedInstances[self::TYPE_KIND_ENUM_NAME] ??= new EnumType([
448448
'name' => self::TYPE_KIND_ENUM_NAME,
449449
'isIntrospection' => true,
450450
'description' => 'An enum describing what kind of type a given `__Type` is.',
@@ -488,7 +488,7 @@ public static function _typeKind(): EnumType
488488
/** @throws InvariantViolation */
489489
public static function _field(): ObjectType
490490
{
491-
return self::$map[self::FIELD_OBJECT_NAME] ??= new ObjectType([
491+
return self::$cachedInstances[self::FIELD_OBJECT_NAME] ??= new ObjectType([
492492
'name' => self::FIELD_OBJECT_NAME,
493493
'isIntrospection' => true,
494494
'description' => 'Object and Interface types are described by a list of Fields, each of '
@@ -542,7 +542,7 @@ public static function _field(): ObjectType
542542
/** @throws InvariantViolation */
543543
public static function _inputValue(): ObjectType
544544
{
545-
return self::$map[self::INPUT_VALUE_OBJECT_NAME] ??= new ObjectType([
545+
return self::$cachedInstances[self::INPUT_VALUE_OBJECT_NAME] ??= new ObjectType([
546546
'name' => self::INPUT_VALUE_OBJECT_NAME,
547547
'isIntrospection' => true,
548548
'description' => 'Arguments provided to Fields or Directives and the input fields of an '
@@ -600,7 +600,7 @@ public static function _inputValue(): ObjectType
600600
/** @throws InvariantViolation */
601601
public static function _enumValue(): ObjectType
602602
{
603-
return self::$map[self::ENUM_VALUE_OBJECT_NAME] ??= new ObjectType([
603+
return self::$cachedInstances[self::ENUM_VALUE_OBJECT_NAME] ??= new ObjectType([
604604
'name' => self::ENUM_VALUE_OBJECT_NAME,
605605
'isIntrospection' => true,
606606
'description' => 'One possible value for a given Enum. Enum values are unique values, not '
@@ -630,7 +630,7 @@ public static function _enumValue(): ObjectType
630630
/** @throws InvariantViolation */
631631
public static function _directive(): ObjectType
632632
{
633-
return self::$map[self::DIRECTIVE_OBJECT_NAME] ??= new ObjectType([
633+
return self::$cachedInstances[self::DIRECTIVE_OBJECT_NAME] ??= new ObjectType([
634634
'name' => self::DIRECTIVE_OBJECT_NAME,
635635
'isIntrospection' => true,
636636
'description' => 'A Directive provides a way to describe alternate runtime execution and '
@@ -669,7 +669,7 @@ public static function _directive(): ObjectType
669669
/** @throws InvariantViolation */
670670
public static function _directiveLocation(): EnumType
671671
{
672-
return self::$map[self::DIRECTIVE_LOCATION_ENUM_NAME] ??= new EnumType([
672+
return self::$cachedInstances[self::DIRECTIVE_LOCATION_ENUM_NAME] ??= new EnumType([
673673
'name' => self::DIRECTIVE_LOCATION_ENUM_NAME,
674674
'isIntrospection' => true,
675675
'description' => 'A Directive can be adjacent to many parts of the GraphQL language, a '
@@ -758,7 +758,7 @@ public static function _directiveLocation(): EnumType
758758
/** @throws InvariantViolation */
759759
public static function schemaMetaFieldDef(): FieldDefinition
760760
{
761-
return self::$map[self::SCHEMA_FIELD_NAME] ??= new FieldDefinition([
761+
return self::$cachedInstances[self::SCHEMA_FIELD_NAME] ??= new FieldDefinition([
762762
'name' => self::SCHEMA_FIELD_NAME,
763763
'type' => Type::nonNull(self::_schema()),
764764
'description' => 'Access the current type schema of this server.',
@@ -770,7 +770,7 @@ public static function schemaMetaFieldDef(): FieldDefinition
770770
/** @throws InvariantViolation */
771771
public static function typeMetaFieldDef(): FieldDefinition
772772
{
773-
return self::$map[self::TYPE_FIELD_NAME] ??= new FieldDefinition([
773+
return self::$cachedInstances[self::TYPE_FIELD_NAME] ??= new FieldDefinition([
774774
'name' => self::TYPE_FIELD_NAME,
775775
'type' => self::_type(),
776776
'description' => 'Request the type information of a single type.',
@@ -787,12 +787,17 @@ public static function typeMetaFieldDef(): FieldDefinition
787787
/** @throws InvariantViolation */
788788
public static function typeNameMetaFieldDef(): FieldDefinition
789789
{
790-
return self::$map[self::TYPE_NAME_FIELD_NAME] ??= new FieldDefinition([
790+
return self::$cachedInstances[self::TYPE_NAME_FIELD_NAME] ??= new FieldDefinition([
791791
'name' => self::TYPE_NAME_FIELD_NAME,
792792
'type' => Type::nonNull(Type::string()),
793793
'description' => 'The name of the current Object type at runtime.',
794794
'args' => [],
795795
'resolve' => static fn ($source, array $args, $context, ResolveInfo $info): string => $info->parentType->name,
796796
]);
797797
}
798+
799+
public static function resetCachedInstances(): void
800+
{
801+
self::$cachedInstances = null;
802+
}
798803
}

0 commit comments

Comments
 (0)