Skip to content

Commit

Permalink
Reset static caches of instances using standard types when overriding…
Browse files Browse the repository at this point in the history
… them
  • Loading branch information
ruudk authored Nov 13, 2024
1 parent accee09 commit a167afa
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 92 deletions.
29 changes: 19 additions & 10 deletions src/Executor/ReferenceExecutor.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ class ReferenceExecutor implements ExecutorImplementation
*/
protected \SplObjectStorage $fieldArgsCache;

protected FieldDefinition $schemaMetaFieldDef;

protected FieldDefinition $typeMetaFieldDef;

protected FieldDefinition $typeNameMetaFieldDef;

protected function __construct(ExecutionContext $context)
{
if (! isset(static::$UNDEFINED)) {
Expand Down Expand Up @@ -701,23 +707,26 @@ protected function resolveField(
*/
protected function getFieldDef(Schema $schema, ObjectType $parentType, string $fieldName): ?FieldDefinition
{
static $schemaMetaFieldDef, $typeMetaFieldDef, $typeNameMetaFieldDef;
$schemaMetaFieldDef ??= Introspection::schemaMetaFieldDef();
$typeMetaFieldDef ??= Introspection::typeMetaFieldDef();
$typeNameMetaFieldDef ??= Introspection::typeNameMetaFieldDef();
$this->schemaMetaFieldDef ??= Introspection::schemaMetaFieldDef();
$this->typeMetaFieldDef ??= Introspection::typeMetaFieldDef();
$this->typeNameMetaFieldDef ??= Introspection::typeNameMetaFieldDef();

$queryType = $schema->getQueryType();

if ($fieldName === $schemaMetaFieldDef->name && $queryType === $parentType) {
return $schemaMetaFieldDef;
if ($fieldName === $this->schemaMetaFieldDef->name
&& $queryType === $parentType
) {
return $this->schemaMetaFieldDef;
}

if ($fieldName === $typeMetaFieldDef->name && $queryType === $parentType) {
return $typeMetaFieldDef;
if ($fieldName === $this->typeMetaFieldDef->name
&& $queryType === $parentType
) {
return $this->typeMetaFieldDef;
}

if ($fieldName === $typeNameMetaFieldDef->name) {
return $typeNameMetaFieldDef;
if ($fieldName === $this->typeNameMetaFieldDef->name) {
return $this->typeNameMetaFieldDef;
}

return $parentType->findField($fieldName);
Expand Down
127 changes: 63 additions & 64 deletions src/Type/Definition/Directive.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ class Directive
/**
* Lazily initialized.
*
* @var array<string, Directive>
* @var array<string, Directive>|null
*/
protected static array $internalDirectives;
protected static ?array $internalDirectives = null;

public string $name;

Expand Down Expand Up @@ -75,91 +75,90 @@ public function __construct(array $config)
$this->config = $config;
}

/** @throws InvariantViolation */
public static function includeDirective(): Directive
{
$internal = self::getInternalDirectives();

return $internal['include'];
}

/**
* @throws InvariantViolation
*
* @return array<string, 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,
],
],
]),
return [
self::INCLUDE_NAME => self::includeDirective(),
self::SKIP_NAME => self::skipDirective(),
self::DEPRECATED_NAME => self::deprecatedDirective(),
];
}

/** @throws InvariantViolation */
public static function skipDirective(): Directive
public static function includeDirective(): Directive
{
$internal = self::getInternalDirectives();
return self::$internalDirectives[self::INCLUDE_NAME] ??= 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.',
],
],
]);
}

return $internal['skip'];
/** @throws InvariantViolation */
public static function skipDirective(): Directive
{
return self::$internalDirectives[self::SKIP_NAME] ??= 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.',
],
],
]);
}

/** @throws InvariantViolation */
public static function deprecatedDirective(): Directive
{
$internal = self::getInternalDirectives();

return $internal['deprecated'];
return self::$internalDirectives[self::DEPRECATED_NAME] ??= 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 isSpecifiedDirective(Directive $directive): bool
{
return \array_key_exists($directive->name, self::getInternalDirectives());
}

public static function resetCachedInstances(): void
{
self::$internalDirectives = null;
}
}
16 changes: 11 additions & 5 deletions src/Type/Definition/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ abstract class Type implements \JsonSerializable
...Introspection::TYPE_NAMES,
];

/** @var array<string, ScalarType> */
protected static array $standardTypes;
/** @var array<string, ScalarType>|null */
protected static ?array $standardTypes;

/** @var array<string, Type&NamedType>|null */
protected static ?array $builtInTypes;

/**
* @api
Expand Down Expand Up @@ -116,9 +119,7 @@ public static function nonNull($type): NonNull
*/
public static function builtInTypes(): array
{
static $builtInTypes;

return $builtInTypes ??= \array_merge(
return self::$builtInTypes ??= \array_merge(
Introspection::getTypes(),
self::getStandardTypes()
);
Expand Down Expand Up @@ -149,6 +150,11 @@ public static function getStandardTypes(): array
*/
public static function overrideStandardTypes(array $types): void
{
// Reset caches that might contain instances of standard types
static::$builtInTypes = null;
Introspection::resetCachedInstances();
Directive::resetCachedInstances();

foreach ($types as $type) {
// @phpstan-ignore-next-line generic type is not enforced by PHP
if (! $type instanceof ScalarType) {
Expand Down
31 changes: 18 additions & 13 deletions src/Type/Introspection.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ class Introspection
self::DIRECTIVE_LOCATION_ENUM_NAME,
];

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

/**
* @param IntrospectionOptions $options
Expand Down Expand Up @@ -253,7 +253,7 @@ public static function getTypes(): array
/** @throws InvariantViolation */
public static function _schema(): ObjectType
{
return self::$map[self::SCHEMA_OBJECT_NAME] ??= new ObjectType([
return self::$cachedInstances[self::SCHEMA_OBJECT_NAME] ??= new ObjectType([
'name' => self::SCHEMA_OBJECT_NAME,
'isIntrospection' => true,
'description' => 'A GraphQL Schema defines the capabilities of a GraphQL '
Expand Down Expand Up @@ -293,7 +293,7 @@ public static function _schema(): ObjectType
/** @throws InvariantViolation */
public static function _type(): ObjectType
{
return self::$map[self::TYPE_OBJECT_NAME] ??= new ObjectType([
return self::$cachedInstances[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 '
Expand Down Expand Up @@ -444,7 +444,7 @@ public static function _type(): ObjectType
/** @throws InvariantViolation */
public static function _typeKind(): EnumType
{
return self::$map[self::TYPE_KIND_ENUM_NAME] ??= new EnumType([
return self::$cachedInstances[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.',
Expand Down Expand Up @@ -488,7 +488,7 @@ public static function _typeKind(): EnumType
/** @throws InvariantViolation */
public static function _field(): ObjectType
{
return self::$map[self::FIELD_OBJECT_NAME] ??= new ObjectType([
return self::$cachedInstances[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 '
Expand Down Expand Up @@ -542,7 +542,7 @@ public static function _field(): ObjectType
/** @throws InvariantViolation */
public static function _inputValue(): ObjectType
{
return self::$map[self::INPUT_VALUE_OBJECT_NAME] ??= new ObjectType([
return self::$cachedInstances[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 '
Expand Down Expand Up @@ -600,7 +600,7 @@ public static function _inputValue(): ObjectType
/** @throws InvariantViolation */
public static function _enumValue(): ObjectType
{
return self::$map[self::ENUM_VALUE_OBJECT_NAME] ??= new ObjectType([
return self::$cachedInstances[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 '
Expand Down Expand Up @@ -630,7 +630,7 @@ public static function _enumValue(): ObjectType
/** @throws InvariantViolation */
public static function _directive(): ObjectType
{
return self::$map[self::DIRECTIVE_OBJECT_NAME] ??= new ObjectType([
return self::$cachedInstances[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 '
Expand Down Expand Up @@ -669,7 +669,7 @@ public static function _directive(): ObjectType
/** @throws InvariantViolation */
public static function _directiveLocation(): EnumType
{
return self::$map[self::DIRECTIVE_LOCATION_ENUM_NAME] ??= new EnumType([
return self::$cachedInstances[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 '
Expand Down Expand Up @@ -758,7 +758,7 @@ public static function _directiveLocation(): EnumType
/** @throws InvariantViolation */
public static function schemaMetaFieldDef(): FieldDefinition
{
return self::$map[self::SCHEMA_FIELD_NAME] ??= new FieldDefinition([
return self::$cachedInstances[self::SCHEMA_FIELD_NAME] ??= new FieldDefinition([
'name' => self::SCHEMA_FIELD_NAME,
'type' => Type::nonNull(self::_schema()),
'description' => 'Access the current type schema of this server.',
Expand All @@ -770,7 +770,7 @@ public static function schemaMetaFieldDef(): FieldDefinition
/** @throws InvariantViolation */
public static function typeMetaFieldDef(): FieldDefinition
{
return self::$map[self::TYPE_FIELD_NAME] ??= new FieldDefinition([
return self::$cachedInstances[self::TYPE_FIELD_NAME] ??= new FieldDefinition([
'name' => self::TYPE_FIELD_NAME,
'type' => self::_type(),
'description' => 'Request the type information of a single type.',
Expand All @@ -787,12 +787,17 @@ public static function typeMetaFieldDef(): FieldDefinition
/** @throws InvariantViolation */
public static function typeNameMetaFieldDef(): FieldDefinition
{
return self::$map[self::TYPE_NAME_FIELD_NAME] ??= new FieldDefinition([
return self::$cachedInstances[self::TYPE_NAME_FIELD_NAME] ??= new FieldDefinition([
'name' => self::TYPE_NAME_FIELD_NAME,
'type' => Type::nonNull(Type::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,
]);
}

public static function resetCachedInstances(): void
{
self::$cachedInstances = null;
}
}
Loading

0 comments on commit a167afa

Please sign in to comment.