Skip to content

Commit 9e7e394

Browse files
committed
Allow deprecating input fields and arguments
1 parent dbc3ef0 commit 9e7e394

16 files changed

+534
-28
lines changed

phpstan-baseline.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ parameters:
2626
path: src/Language/Visitor.php
2727

2828
-
29-
message: "#^Parameter \\#1 \\$config of class GraphQL\\\\Type\\\\Definition\\\\Argument constructor expects array\\{name\\: string, type\\: \\(callable\\(\\)\\: GraphQL\\\\Type\\\\Definition\\\\InputType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|\\(GraphQL\\\\Type\\\\Definition\\\\InputType&GraphQL\\\\Type\\\\Definition\\\\Type\\), defaultValue\\?\\: mixed, description\\?\\: string\\|null, astNode\\?\\: GraphQL\\\\Language\\\\AST\\\\InputValueDefinitionNode\\|null\\}, non\\-empty\\-array given\\.$#"
29+
message: "#^Parameter \\#1 \\$config of class GraphQL\\\\Type\\\\Definition\\\\Argument constructor expects array\\{name\\: string, type\\: \\(callable\\(\\)\\: GraphQL\\\\Type\\\\Definition\\\\InputType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|\\(GraphQL\\\\Type\\\\Definition\\\\InputType&GraphQL\\\\Type\\\\Definition\\\\Type\\), defaultValue\\?\\: mixed, description\\?\\: string\\|null, deprecationReason\\?\\: string\\|null, astNode\\?\\: GraphQL\\\\Language\\\\AST\\\\InputValueDefinitionNode\\|null\\}, non\\-empty\\-array given\\.$#"
3030
count: 1
3131
path: src/Type/Definition/Argument.php
3232

src/Type/Definition/Argument.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414
* type: ArgumentType,
1515
* defaultValue?: mixed,
1616
* description?: string|null,
17+
* deprecationReason?: string|null,
1718
* astNode?: InputValueDefinitionNode|null
1819
* }
1920
* @phpstan-type ArgumentConfig array{
2021
* name: string,
2122
* type: ArgumentType,
2223
* defaultValue?: mixed,
2324
* description?: string|null,
25+
* deprecationReason?: string|null,
2426
* astNode?: InputValueDefinitionNode|null
2527
* }
2628
* @phpstan-type ArgumentListConfig iterable<ArgumentConfig|ArgumentType>|iterable<UnnamedArgumentConfig>
@@ -34,6 +36,8 @@ class Argument
3436

3537
public ?string $description;
3638

39+
public ?string $deprecationReason;
40+
3741
/** @var Type&InputType */
3842
private Type $type;
3943

@@ -48,6 +52,7 @@ public function __construct(array $config)
4852
$this->name = $config['name'];
4953
$this->defaultValue = $config['defaultValue'] ?? null;
5054
$this->description = $config['description'] ?? null;
55+
$this->deprecationReason = $config['deprecationReason'] ?? null;
5156
// Do nothing for type, it is lazy loaded in getType()
5257
$this->astNode = $config['astNode'] ?? null;
5358

@@ -95,6 +100,11 @@ public function isRequired(): bool
95100
&& ! $this->defaultValueExists();
96101
}
97102

103+
public function isDeprecated(): bool
104+
{
105+
return (bool) $this->deprecationReason;
106+
}
107+
98108
/**
99109
* @param Type&NamedType $parentType
100110
*
@@ -113,5 +123,9 @@ public function assertValid(FieldDefinition $parentField, Type $parentType): voi
113123
$notInputType = Utils::printSafe($this->type);
114124
throw new InvariantViolation("{$parentType->name}.{$parentField->name}({$this->name}): argument type must be Input Type but got: {$notInputType}");
115125
}
126+
127+
if ($this->isRequired() && $this->isDeprecated()) {
128+
throw new InvariantViolation("Required argument {$parentType->name}.{$parentField->name}({$this->name}:) cannot be deprecated.");
129+
}
116130
}
117131
}

src/Type/Definition/Directive.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ public static function getInternalDirectives(): array
127127
'locations' => [
128128
DirectiveLocation::FIELD_DEFINITION,
129129
DirectiveLocation::ENUM_VALUE,
130+
DirectiveLocation::ARGUMENT_DEFINITION,
131+
DirectiveLocation::INPUT_FIELD_DEFINITION,
130132
],
131133
'args' => [
132134
self::REASON_ARGUMENT_NAME => [

src/Type/Definition/InputObjectField.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414
* type: ArgumentType,
1515
* defaultValue?: mixed,
1616
* description?: string|null,
17+
* deprecationReason?: string|null,
1718
* astNode?: InputValueDefinitionNode|null
1819
* }
1920
* @phpstan-type UnnamedInputObjectFieldConfig array{
2021
* name?: string,
2122
* type: ArgumentType,
2223
* defaultValue?: mixed,
2324
* description?: string|null,
25+
* deprecationReason?: string|null,
2426
* astNode?: InputValueDefinitionNode|null
2527
* }
2628
*/
@@ -33,6 +35,8 @@ class InputObjectField
3335

3436
public ?string $description;
3537

38+
public ?string $deprecationReason;
39+
3640
/** @var Type&InputType */
3741
private Type $type;
3842

@@ -47,6 +51,7 @@ public function __construct(array $config)
4751
$this->name = $config['name'];
4852
$this->defaultValue = $config['defaultValue'] ?? null;
4953
$this->description = $config['description'] ?? null;
54+
$this->deprecationReason = $config['deprecationReason'] ?? null;
5055
// Do nothing for type, it is lazy loaded in getType()
5156
$this->astNode = $config['astNode'] ?? null;
5257

@@ -74,6 +79,11 @@ public function isRequired(): bool
7479
&& ! $this->defaultValueExists();
7580
}
7681

82+
public function isDeprecated(): bool
83+
{
84+
return (bool) $this->deprecationReason;
85+
}
86+
7787
/**
7888
* @param Type&NamedType $parentType
7989
*
@@ -97,5 +107,9 @@ public function assertValid(Type $parentType): void
97107
if (\array_key_exists('resolve', $this->config)) {
98108
throw new InvariantViolation("{$parentType->name}.{$this->name} field has a resolve property, but Input Types cannot define resolvers.");
99109
}
110+
111+
if ($this->isRequired() && $this->isDeprecated()) {
112+
throw new InvariantViolation("Required input field {$parentType->name}.{$this->name} cannot be deprecated.");
113+
}
100114
}
101115
}

src/Type/Introspection.php

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public static function getIntrospectionQuery(array $options = []): string
9696
fields(includeDeprecated: true) {
9797
name
9898
{$descriptions}
99-
args {
99+
args(includeDeprecated: true) {
100100
...InputValue
101101
}
102102
type {
@@ -105,7 +105,7 @@ public static function getIntrospectionQuery(array $options = []): string
105105
isDeprecated
106106
deprecationReason
107107
}
108-
inputFields {
108+
inputFields(includeDeprecated: true) {
109109
...InputValue
110110
}
111111
interfaces {
@@ -127,6 +127,8 @@ enumValues(includeDeprecated: true) {
127127
{$descriptions}
128128
type { ...TypeRef }
129129
defaultValue
130+
isDeprecated
131+
deprecationReason
130132
}
131133
132134
fragment TypeRef on __Type {
@@ -392,9 +394,31 @@ static function (EnumValueDefinition $value): bool {
392394
],
393395
'inputFields' => [
394396
'type' => Type::listOf(Type::nonNull(self::_inputValue())),
395-
'resolve' => static fn ($type): ?array => $type instanceof InputObjectType
396-
? $type->getFields()
397-
: null,
397+
'args' => [
398+
'includeDeprecated' => [
399+
'type' => Type::boolean(),
400+
'defaultValue' => false,
401+
],
402+
],
403+
'resolve' => static function ($type, $args): ?array {
404+
if ($type instanceof InputObjectType) {
405+
$fields = $type->getFields();
406+
407+
if (! ($args['includeDeprecated'] ?? false)) {
408+
return \array_filter(
409+
$fields,
410+
static fn (InputObjectField $field): bool =>
411+
$field->deprecationReason === null
412+
|| $field->deprecationReason === '',
413+
414+
);
415+
}
416+
417+
return $fields;
418+
}
419+
420+
return null;
421+
},
398422
],
399423
'ofType' => [
400424
'type' => self::_type(),
@@ -469,7 +493,27 @@ public static function _field(): ObjectType
469493
],
470494
'args' => [
471495
'type' => Type::nonNull(Type::listOf(Type::nonNull(self::_inputValue()))),
472-
'resolve' => static fn (FieldDefinition $field): array => $field->args,
496+
'args' => [
497+
'includeDeprecated' => [
498+
'type' => Type::boolean(),
499+
'defaultValue' => false,
500+
],
501+
],
502+
'resolve' => static function (FieldDefinition $field, $args): array {
503+
$values = $field->args;
504+
505+
if (! ($args['includeDeprecated'] ?? false)) {
506+
return \array_filter(
507+
$values,
508+
static fn (Argument $value): bool =>
509+
$value->deprecationReason === null
510+
|| $value->deprecationReason === '',
511+
512+
);
513+
}
514+
515+
return $values;
516+
},
473517
],
474518
'type' => [
475519
'type' => Type::nonNull(self::_type()),
@@ -532,6 +576,17 @@ public static function _inputValue(): ObjectType
532576
return null;
533577
},
534578
],
579+
'isDeprecated' => [
580+
'type' => Type::nonNull(Type::boolean()),
581+
/** @param Argument|InputObjectField $inputValue */
582+
'resolve' => static fn ($inputValue): bool => $inputValue->deprecationReason !== null
583+
&& $inputValue->deprecationReason !== '',
584+
],
585+
'deprecationReason' => [
586+
'type' => Type::string(),
587+
/** @param Argument|InputObjectField $inputValue */
588+
'resolve' => static fn ($inputValue): ?string => $inputValue->deprecationReason,
589+
],
535590
],
536591
]);
537592
}

src/Utils/ASTDefinitionBuilder.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ private function makeInputValues(NodeList $values): array
141141
'name' => $value->name->value,
142142
'type' => $type,
143143
'description' => $value->description->value ?? null,
144+
'deprecationReason' => $this->getDeprecationReason($value),
144145
'astNode' => $value,
145146
];
146147

@@ -388,7 +389,7 @@ public function buildField(FieldDefinitionNode $field): array
388389
* Given a collection of directives, returns the string value for the
389390
* deprecation reason.
390391
*
391-
* @param EnumValueDefinitionNode|FieldDefinitionNode $node
392+
* @param EnumValueDefinitionNode|FieldDefinitionNode|InputValueDefinitionNode $node
392393
*
393394
* @throws \Exception
394395
* @throws \ReflectionException

src/Utils/SchemaExtender.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ protected function extendInputFieldMap(InputObjectType $type): array
300300
$newFieldConfig = [
301301
'description' => $field->description,
302302
'type' => $extendedType,
303+
'deprecationReason' => $field->deprecationReason,
303304
'astNode' => $field->astNode,
304305
];
305306

@@ -459,6 +460,7 @@ protected function extendArgs(array $args): array
459460
$def = [
460461
'type' => $extendedType,
461462
'description' => $arg->description,
463+
'deprecationReason' => $arg->deprecationReason,
462464
'astNode' => $arg->astNode,
463465
];
464466

src/Utils/SchemaPrinter.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ protected static function printArgs(array $options, array $args, string $indenta
336336
*/
337337
protected static function printInputValue($arg): string
338338
{
339-
$argDecl = "{$arg->name}: {$arg->getType()->toString()}";
339+
$argDecl = "{$arg->name}: {$arg->getType()->toString()}" . static::printDeprecated($arg);
340340

341341
if ($arg->defaultValueExists()) {
342342
$defaultValueAST = AST::astFromValue($arg->defaultValue, $arg->getType());
@@ -424,15 +424,15 @@ protected static function printFields(array $options, $type): string
424424
}
425425

426426
/**
427-
* @param FieldDefinition|EnumValueDefinition $fieldOrEnumVal
427+
* @param FieldDefinition|EnumValueDefinition|InputObjectField|Argument $deprecation
428428
*
429429
* @throws \JsonException
430430
* @throws InvariantViolation
431431
* @throws SerializationError
432432
*/
433-
protected static function printDeprecated($fieldOrEnumVal): string
433+
protected static function printDeprecated($deprecation): string
434434
{
435-
$reason = $fieldOrEnumVal->deprecationReason;
435+
$reason = $deprecation->deprecationReason;
436436
if ($reason === null) {
437437
return '';
438438
}

tests/Type/DefinitionTest.php

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -920,13 +920,19 @@ public function testAcceptsAnObjectTypeWithFieldArgs(): void
920920
'goodField' => [
921921
'type' => Type::string(),
922922
'args' => [
923-
'goodArg' => ['type' => Type::string()],
923+
'goodArg' => [
924+
'type' => Type::string(),
925+
'deprecationReason' => 'Just because',
926+
],
924927
],
925928
],
926929
],
927930
]);
928931
$objType->assertValid();
929-
self::assertDidNotCrash();
932+
$argument = $objType->getField('goodField')->getArg('goodArg');
933+
self::assertInstanceOf(Argument::class, $argument);
934+
self::assertTrue($argument->isDeprecated());
935+
self::assertSame('Just because', $argument->deprecationReason);
930936
}
931937

932938
// Object interfaces must be array
@@ -1436,12 +1442,16 @@ public function testAcceptsAnInputObjectTypeWithFields(): void
14361442
'fields' => [
14371443
$fieldName => [
14381444
'type' => Type::string(),
1445+
'deprecationReason' => 'Just because',
14391446
],
14401447
],
14411448
]);
14421449

14431450
$inputObjType->assertValid();
1444-
self::assertSame(Type::string(), $inputObjType->getField($fieldName)->getType());
1451+
$field = $inputObjType->getField($fieldName);
1452+
self::assertSame(Type::string(), $field->getType());
1453+
self::assertTrue($field->isDeprecated());
1454+
self::assertSame('Just because', $field->deprecationReason);
14451455
}
14461456

14471457
/** @see it('accepts an Input Object type with a field function') */
@@ -1453,12 +1463,16 @@ public function testAcceptsAnInputObjectTypeWithAFieldFunction(): void
14531463
'fields' => static fn (): array => [
14541464
$fieldName => [
14551465
'type' => Type::string(),
1466+
'deprecationReason' => 'Just because',
14561467
],
14571468
],
14581469
]);
14591470

14601471
$inputObjType->assertValid();
1472+
$field = $inputObjType->getField($fieldName);
14611473
self::assertSame(Type::string(), $inputObjType->getField($fieldName)->getType());
1474+
self::assertTrue($field->isDeprecated());
1475+
self::assertSame('Just because', $field->deprecationReason);
14621476
}
14631477

14641478
/** @see it('accepts an Input Object type with a field type function') */

0 commit comments

Comments
 (0)