Skip to content

Commit 2af585f

Browse files
authored
Added ability to control field visibility on schema definition (#1434)
1 parent d86cefa commit 2af585f

File tree

14 files changed

+176
-32
lines changed

14 files changed

+176
-32
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ bench: ## Runs benchmarks with phpbench
3131
.PHONY: docs
3232
docs: ## Generate the class-reference docs
3333
php generate-class-reference.php
34+
prettier --write docs/class-reference.md
3435

3536
vendor: composer.json composer.lock
3637
composer install

docs/type-definitions/object-types.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,15 @@ This example uses **inline** style for Object Type definitions, but you can also
5858

5959
## Configuration options
6060

61-
| Option | Type | Notes |
62-
| ------------ | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
63-
| name | `string` | **Required.** Unique name of this object type within Schema |
64-
| fields | `array` or `callable` | **Required**. An array describing object fields or callable returning such an array. See [field configuration options](#field-configuration-options) section below for expected structure of each array entry. See also the section on [Circular types](#recurring-and-circular-types) for an explanation of when to use callable for this option. |
65-
| description | `string` | Plain-text description of this type for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation) |
66-
| interfaces | `array` or `callable` | List of interfaces implemented by this type or callable returning such a list. See [Interface Types](interfaces.md) for details. See also the section on [Circular types](#recurring-and-circular-types) for an explanation of when to use callable for this option. |
67-
| isTypeOf | `callable` | **function ($value, $context, [ResolveInfo](../class-reference.md#graphqltypedefinitionresolveinfo) $info): bool**<br> Expected to return **true** if **$value** qualifies for this type (see section about [Abstract Type Resolution](interfaces.md#interface-role-in-data-fetching) for explanation). |
68-
| resolveField | `callable` | **function ($value, array $args, $context, [ResolveInfo](../class-reference.md#graphqltypedefinitionresolveinfo) $info): mixed**<br> Given the **$value** of this type, it is expected to return value for a field defined in **$info->fieldName**. A good place to define a type-specific strategy for field resolution. See section on [Data Fetching](../data-fetching.md) for details. |
61+
| Option | Type | Notes |
62+
|--------------|------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
63+
| name | `string` | **Required.** Unique name of this object type within Schema |
64+
| fields | `array` or `callable` | **Required**. An array describing object fields or callable returning such an array. See [field configuration options](#field-configuration-options) section below for expected structure of each array entry. See also the section on [Circular types](#recurring-and-circular-types) for an explanation of when to use callable for this option. |
65+
| description | `string` | Plain-text description of this type for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation) |
66+
| interfaces | `array` or `callable` | List of interfaces implemented by this type or callable returning such a list. See [Interface Types](interfaces.md) for details. See also the section on [Circular types](#recurring-and-circular-types) for an explanation of when to use callable for this option. |
67+
| isTypeOf | `callable` | **function ($value, $context, [ResolveInfo](../class-reference.md#graphqltypedefinitionresolveinfo) $info): bool**<br> Expected to return **true** if **$value** qualifies for this type (see section about [Abstract Type Resolution](interfaces.md#interface-role-in-data-fetching) for explanation). |
68+
| resolveField | `callable` | **function ($value, array $args, $context, [ResolveInfo](../class-reference.md#graphqltypedefinitionresolveinfo) $info): mixed**<br> Given the **$value** of this type, it is expected to return value for a field defined in **$info->fieldName**. A good place to define a type-specific strategy for field resolution. See section on [Data Fetching](../data-fetching.md) for details. |
69+
| visible | `bool` or `callable` | Defaults to `true`. The given callable receives no arguments and is expected to return a `bool`, it is called once when the field may be accessed. The field is treated as if it were not defined at all when this is `false`. |
6970

7071
### Field configuration options
7172

src/Executor/ReferenceExecutor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ protected function resolveField(ObjectType $parentType, $rootValue, \ArrayObject
597597

598598
$fieldName = $fieldNode->name->value;
599599
$fieldDef = $this->getFieldDef($exeContext->schema, $parentType, $fieldName);
600-
if ($fieldDef === null) {
600+
if ($fieldDef === null || ! $fieldDef->isVisible()) {
601601
return static::$UNDEFINED;
602602
}
603603

src/Type/Definition/FieldDefinition.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616
*
1717
* @phpstan-type FieldType (Type&OutputType)|callable(): (Type&OutputType)
1818
* @phpstan-type ComplexityFn callable(int, array<string, mixed>): int
19+
* @phpstan-type VisibilityFn callable(): bool
1920
* @phpstan-type FieldDefinitionConfig array{
2021
* name: string,
2122
* type: FieldType,
2223
* resolve?: FieldResolver|null,
2324
* args?: ArgumentListConfig|null,
2425
* description?: string|null,
26+
* visible?: VisibilityFn|bool,
2527
* deprecationReason?: string|null,
2628
* astNode?: FieldDefinitionNode|null,
2729
* complexity?: ComplexityFn|null
@@ -31,6 +33,7 @@
3133
* resolve?: FieldResolver|null,
3234
* args?: ArgumentListConfig|null,
3335
* description?: string|null,
36+
* visible?: VisibilityFn|bool,
3437
* deprecationReason?: string|null,
3538
* astNode?: FieldDefinitionNode|null,
3639
* complexity?: ComplexityFn|null
@@ -64,6 +67,13 @@ class FieldDefinition
6467

6568
public ?string $description;
6669

70+
/**
71+
* @var callable|bool
72+
*
73+
* @phpstan-var VisibilityFn|bool
74+
*/
75+
public $visible;
76+
6777
public ?string $deprecationReason;
6878

6979
public ?FieldDefinitionNode $astNode;
@@ -94,6 +104,7 @@ public function __construct(array $config)
94104
? Argument::listFromConfig($config['args'])
95105
: [];
96106
$this->description = $config['description'] ?? null;
107+
$this->visible = $config['visible'] ?? true;
97108
$this->deprecationReason = $config['deprecationReason'] ?? null;
98109
$this->astNode = $config['astNode'] ?? null;
99110
$this->complexityFn = $config['complexity'] ?? null;
@@ -181,6 +192,15 @@ public function getType(): Type
181192
return $this->type ??= Schema::resolveType($this->config['type']);
182193
}
183194

195+
public function isVisible(): bool
196+
{
197+
if (is_bool($this->visible)) {
198+
return $this->visible;
199+
}
200+
201+
return $this->visible = ($this->visible)();
202+
}
203+
184204
public function isDeprecated(): bool
185205
{
186206
return (bool) $this->deprecationReason;

src/Type/Definition/HasFieldsType.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ public function findField(string $name): ?FieldDefinition;
2121
public function getFields(): array;
2222

2323
/**
24+
* @throws InvariantViolation
25+
*
26+
* @return array<string, FieldDefinition>
27+
*/
28+
public function getVisibleFields(): array;
29+
30+
/**
31+
* Get all field names, including only visible fields.
32+
*
2433
* @throws InvariantViolation
2534
*
2635
* @return array<int, string>

src/Type/Definition/HasFieldsTypeImplementation.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,24 @@ public function getFields(): array
7878
return $this->fields;
7979
}
8080

81+
public function getVisibleFields(): array
82+
{
83+
return array_filter(
84+
$this->getFields(),
85+
fn (FieldDefinition $fieldDefinition): bool => $fieldDefinition->isVisible()
86+
);
87+
}
88+
8189
/** @throws InvariantViolation */
8290
public function getFieldNames(): array
8391
{
8492
$this->initializeFields();
8593

86-
return \array_keys($this->fields);
94+
$visibleFieldNames = array_map(
95+
fn (FieldDefinition $fieldDefinition): string => $fieldDefinition->getName(),
96+
$this->getVisibleFields()
97+
);
98+
99+
return array_values($visibleFieldNames);
87100
}
88101
}

src/Type/Introspection.php

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -354,13 +354,12 @@ public static function _type(): ObjectType
354354
],
355355
'resolve' => static function (Type $type, $args): ?array {
356356
if ($type instanceof ObjectType || $type instanceof InterfaceType) {
357-
$fields = $type->getFields();
357+
$fields = $type->getVisibleFields();
358358

359359
if (! ($args['includeDeprecated'] ?? false)) {
360-
$fields = \array_filter(
360+
return \array_filter(
361361
$fields,
362-
static fn (FieldDefinition $field): bool => $field->deprecationReason === null
363-
|| $field->deprecationReason === ''
362+
static fn (FieldDefinition $field): bool => ! $field->isDeprecated()
364363
);
365364
}
366365

@@ -397,10 +396,7 @@ public static function _type(): ObjectType
397396
if (! ($args['includeDeprecated'] ?? false)) {
398397
return \array_filter(
399398
$values,
400-
static function (EnumValueDefinition $value): bool {
401-
return $value->deprecationReason === null
402-
|| $value->deprecationReason === '';
403-
}
399+
static fn (EnumValueDefinition $value): bool => ! $value->isDeprecated()
404400
);
405401
}
406402

@@ -425,8 +421,7 @@ static function (EnumValueDefinition $value): bool {
425421
if (! ($args['includeDeprecated'] ?? false)) {
426422
return \array_filter(
427423
$fields,
428-
static fn (InputObjectField $field): bool => $field->deprecationReason === null
429-
|| $field->deprecationReason === '',
424+
static fn (InputObjectField $field): bool => ! $field->isDeprecated(),
430425
);
431426
}
432427

@@ -521,8 +516,7 @@ public static function _field(): ObjectType
521516
if (! ($args['includeDeprecated'] ?? false)) {
522517
return \array_filter(
523518
$values,
524-
static fn (Argument $value): bool => $value->deprecationReason === null
525-
|| $value->deprecationReason === '',
519+
static fn (Argument $value): bool => ! $value->isDeprecated(),
526520
);
527521
}
528522

@@ -535,8 +529,7 @@ public static function _field(): ObjectType
535529
],
536530
'isDeprecated' => [
537531
'type' => Type::nonNull(Type::boolean()),
538-
'resolve' => static fn (FieldDefinition $field): bool => $field->deprecationReason !== null
539-
&& $field->deprecationReason !== '',
532+
'resolve' => static fn (FieldDefinition $field): bool => $field->isDeprecated(),
540533
],
541534
'deprecationReason' => [
542535
'type' => Type::string(),
@@ -593,8 +586,7 @@ public static function _inputValue(): ObjectType
593586
'isDeprecated' => [
594587
'type' => Type::nonNull(Type::boolean()),
595588
/** @param Argument|InputObjectField $inputValue */
596-
'resolve' => static fn ($inputValue): bool => $inputValue->deprecationReason !== null
597-
&& $inputValue->deprecationReason !== '',
589+
'resolve' => static fn ($inputValue): bool => $inputValue->isDeprecated(),
598590
],
599591
'deprecationReason' => [
600592
'type' => Type::string(),
@@ -625,8 +617,7 @@ public static function _enumValue(): ObjectType
625617
],
626618
'isDeprecated' => [
627619
'type' => Type::nonNull(Type::boolean()),
628-
'resolve' => static fn (EnumValueDefinition $enumValue): bool => $enumValue->deprecationReason !== null
629-
&& $enumValue->deprecationReason !== '',
620+
'resolve' => static fn (EnumValueDefinition $enumValue): bool => $enumValue->isDeprecated(),
630621
],
631622
'deprecationReason' => [
632623
'type' => Type::string(),

src/Validator/Rules/FieldsOnCorrectType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public function getVisitor(QueryValidationContext $context): array
2020
return [
2121
NodeKind::FIELD => function (FieldNode $node) use ($context): void {
2222
$fieldDef = $context->getFieldDef();
23-
if ($fieldDef !== null) {
23+
if ($fieldDef !== null && $fieldDef->isVisible()) {
2424
return;
2525
}
2626

tests/Executor/ExecutorSchemaTest.php

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,22 @@ public function testExecutesUsingASchema(): void
2828
'name' => 'Image',
2929
'fields' => [
3030
'url' => ['type' => Type::string()],
31-
'width' => ['type' => Type::int()],
32-
'height' => ['type' => Type::int()],
31+
'width' => [
32+
'type' => Type::int(),
33+
'visible' => fn (): bool => true,
34+
],
35+
'height' => [
36+
'type' => Type::int(),
37+
'visible' => true,
38+
],
39+
'mimetype' => [
40+
'type' => Type::string(),
41+
'visible' => fn (): bool => false,
42+
],
43+
'size' => [
44+
'type' => Type::string(),
45+
'visible' => false,
46+
],
3347
],
3448
]);
3549

@@ -107,7 +121,8 @@ public function testExecutesUsingASchema(): void
107121
pic(width: 640, height: 480) {
108122
url,
109123
width,
110-
height
124+
height,
125+
mimetype
111126
},
112127
recentArticle {
113128
...articleFields,
@@ -222,6 +237,7 @@ private function article(string $id): array
222237
'url' => "cdn://{$uid}",
223238
'width' => $width,
224239
'height' => $height,
240+
'mimetype' => 'image/gif',
225241
];
226242

227243
$johnSmith = [

tests/StarWarsSchema.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ public static function build(): Schema
129129
'type' => Type::string(),
130130
'description' => 'All secrets about their past.',
131131
],
132+
'secretName' => [
133+
'type' => Type::string(),
134+
'description' => 'The secret name of the character.',
135+
'visible' => false,
136+
],
132137
];
133138
},
134139
'resolveType' => static function (array $obj) use (&$humanType, &$droidType): ObjectType {

0 commit comments

Comments
 (0)