Skip to content

Commit

Permalink
Add field config decorator when building schema from SDL
Browse files Browse the repository at this point in the history
  • Loading branch information
Warxcell authored Oct 23, 2024
1 parent 8ab8daa commit f2748f1
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 20 deletions.
13 changes: 11 additions & 2 deletions docs/class-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2409,6 +2409,7 @@ Build instance of @see \GraphQL\Type\Schema out of schema language definition (s
See [schema definition language docs](schema-definition-language.md) for details.

@phpstan-import-type TypeConfigDecorator from ASTDefinitionBuilder
@phpstan-import-type FieldConfigDecorator from ASTDefinitionBuilder

@phpstan-type BuildSchemaOptions array{
assumeValid?: bool,
Expand Down Expand Up @@ -2439,6 +2440,7 @@ assumeValidSDL?: bool
* @param DocumentNode|Source|string $source
*
* @phpstan-param TypeConfigDecorator|null $typeConfigDecorator
* @phpstan-param FieldConfigDecorator|null $fieldConfigDecorator
*
* @param array<string, bool> $options
*
Expand All @@ -2452,7 +2454,12 @@ 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 = [],
?callable $fieldConfigDecorator = null
): GraphQL\Type\Schema
```

```php
Expand All @@ -2465,6 +2472,7 @@ static function build($source, ?callable $typeConfigDecorator = null, array $opt
* has no resolve methods, so execution will use default resolvers.
*
* @phpstan-param TypeConfigDecorator|null $typeConfigDecorator
* @phpstan-param FieldConfigDecorator|null $fieldConfigDecorator
*
* @param array<string, bool> $options
*
Expand All @@ -2480,7 +2488,8 @@ static function build($source, ?callable $typeConfigDecorator = null, array $opt
static function buildAST(
GraphQL\Language\AST\DocumentNode $ast,
?callable $typeConfigDecorator = null,
array $options = []
array $options = [],
?callable $fieldConfigDecorator = null
): GraphQL\Type\Schema
```

Expand Down
26 changes: 22 additions & 4 deletions src/Utils/ASTDefinitionBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
*
* @phpstan-type ResolveType callable(string, Node|null): Type&NamedType
* @phpstan-type TypeConfigDecorator callable(array<string, mixed>, Node&TypeDefinitionNode, array<string, Node&TypeDefinitionNode>): array<string, mixed>
* @phpstan-type FieldConfigDecorator callable(UnnamedFieldDefinitionConfig, FieldDefinitionNode, ObjectTypeDefinitionNode|ObjectTypeExtensionNode|InterfaceTypeDefinitionNode|InterfaceTypeExtensionNode): UnnamedFieldDefinitionConfig
*/
class ASTDefinitionBuilder
{
Expand All @@ -72,6 +73,13 @@ class ASTDefinitionBuilder
*/
private $typeConfigDecorator;

/**
* @var callable|null
*
* @phpstan-var FieldConfigDecorator|null
*/
private $fieldConfigDecorator;

/** @var array<string, Type&NamedType> */
private array $cache;

Expand All @@ -91,12 +99,14 @@ public function __construct(
array $typeDefinitionsMap,
array $typeExtensionsMap,
callable $resolveType,
?callable $typeConfigDecorator = null
?callable $typeConfigDecorator = null,
?callable $fieldConfigDecorator = null
) {
$this->typeDefinitionsMap = $typeDefinitionsMap;
$this->typeExtensionsMap = $typeExtensionsMap;
$this->resolveType = $resolveType;
$this->typeConfigDecorator = $typeConfigDecorator;
$this->fieldConfigDecorator = $fieldConfigDecorator;

$this->cache = Type::builtInTypes();
}
Expand Down Expand Up @@ -355,34 +365,42 @@ private function makeFieldDefMap(array $nodes): array
$map = [];
foreach ($nodes as $node) {
foreach ($node->fields as $field) {
$map[$field->name->value] = $this->buildField($field);
$map[$field->name->value] = $this->buildField($field, $node);
}
}

return $map;
}

/**
* @param ObjectTypeDefinitionNode|ObjectTypeExtensionNode|InterfaceTypeDefinitionNode|InterfaceTypeExtensionNode $node
*
* @throws \Exception
* @throws Error
*
* @return UnnamedFieldDefinitionConfig
*/
public function buildField(FieldDefinitionNode $field): array
public function buildField(FieldDefinitionNode $field, object $node): array
{
// Note: While this could make assertions to get the correctly typed
// value, that would throw immediately while type system validation
// with validateSchema() will produce more actionable results.
/** @var OutputType&Type $type */
$type = $this->buildWrappedType($field->type);

return [
$config = [
'type' => $type,
'description' => $field->description->value ?? null,
'args' => $this->makeInputValues($field->arguments),
'deprecationReason' => $this->getDeprecationReason($field),
'astNode' => $field,
];

if ($this->fieldConfigDecorator !== null) {
$config = ($this->fieldConfigDecorator)($config, $field, $node);
}

return $config;
}

/**
Expand Down
27 changes: 21 additions & 6 deletions src/Utils/BuildSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
* See [schema definition language docs](schema-definition-language.md) for details.
*
* @phpstan-import-type TypeConfigDecorator from ASTDefinitionBuilder
* @phpstan-import-type FieldConfigDecorator from ASTDefinitionBuilder
*
* @phpstan-type BuildSchemaOptions array{
* assumeValid?: bool,
Expand Down Expand Up @@ -56,6 +57,13 @@ class BuildSchema
*/
private $typeConfigDecorator;

/**
* @var callable|null
*
* @phpstan-var FieldConfigDecorator|null
*/
private $fieldConfigDecorator;

/**
* @var array<string, bool>
*
Expand All @@ -72,11 +80,13 @@ class BuildSchema
public function __construct(
DocumentNode $ast,
?callable $typeConfigDecorator = null,
array $options = []
array $options = [],
?callable $fieldConfigDecorator = null
) {
$this->ast = $ast;
$this->typeConfigDecorator = $typeConfigDecorator;
$this->options = $options;
$this->fieldConfigDecorator = $fieldConfigDecorator;
}

/**
Expand All @@ -86,6 +96,7 @@ public function __construct(
* @param DocumentNode|Source|string $source
*
* @phpstan-param TypeConfigDecorator|null $typeConfigDecorator
* @phpstan-param FieldConfigDecorator|null $fieldConfigDecorator
*
* @param array<string, bool> $options
*
Expand All @@ -102,13 +113,14 @@ public function __construct(
public static function build(
$source,
?callable $typeConfigDecorator = null,
array $options = []
array $options = [],
?callable $fieldConfigDecorator = null
): Schema {
$doc = $source instanceof DocumentNode
? $source
: Parser::parse($source);

return self::buildAST($doc, $typeConfigDecorator, $options);
return self::buildAST($doc, $typeConfigDecorator, $options, $fieldConfigDecorator);
}

/**
Expand All @@ -120,6 +132,7 @@ public static function build(
* has no resolve methods, so execution will use default resolvers.
*
* @phpstan-param TypeConfigDecorator|null $typeConfigDecorator
* @phpstan-param FieldConfigDecorator|null $fieldConfigDecorator
*
* @param array<string, bool> $options
*
Expand All @@ -135,9 +148,10 @@ public static function build(
public static function buildAST(
DocumentNode $ast,
?callable $typeConfigDecorator = null,
array $options = []
array $options = [],
?callable $fieldConfigDecorator = null
): Schema {
return (new self($ast, $typeConfigDecorator, $options))->buildSchema();
return (new self($ast, $typeConfigDecorator, $options, $fieldConfigDecorator))->buildSchema();
}

/**
Expand Down Expand Up @@ -200,7 +214,8 @@ public function buildSchema(): Schema
static function (string $typeName): Type {
throw self::unknownType($typeName);
},
$this->typeConfigDecorator
$this->typeConfigDecorator,
$this->fieldConfigDecorator
);

$directives = \array_map(
Expand Down
16 changes: 11 additions & 5 deletions src/Utils/SchemaExtender.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

/**
* @phpstan-import-type TypeConfigDecorator from ASTDefinitionBuilder
* @phpstan-import-type FieldConfigDecorator from ASTDefinitionBuilder
* @phpstan-import-type UnnamedArgumentConfig from Argument
* @phpstan-import-type UnnamedInputObjectFieldConfig from InputObjectField
*
Expand All @@ -58,6 +59,7 @@ class SchemaExtender
* @param array<string, bool> $options
*
* @phpstan-param TypeConfigDecorator|null $typeConfigDecorator
* @phpstan-param FieldConfigDecorator|null $fieldConfigDecorator
*
* @api
*
Expand All @@ -68,15 +70,17 @@ public static function extend(
Schema $schema,
DocumentNode $documentAST,
array $options = [],
?callable $typeConfigDecorator = null
?callable $typeConfigDecorator = null,
?callable $fieldConfigDecorator = null
): Schema {
return (new static())->doExtend($schema, $documentAST, $options, $typeConfigDecorator);
return (new static())->doExtend($schema, $documentAST, $options, $typeConfigDecorator, $fieldConfigDecorator);
}

/**
* @param array<string, bool> $options
*
* @phpstan-param TypeConfigDecorator|null $typeConfigDecorator
* @phpstan-param FieldConfigDecorator|null $fieldConfigDecorator
*
* @throws \Exception
* @throws \ReflectionException
Expand All @@ -87,7 +91,8 @@ protected function doExtend(
Schema $schema,
DocumentNode $documentAST,
array $options = [],
?callable $typeConfigDecorator = null
?callable $typeConfigDecorator = null,
?callable $fieldConfigDecorator = null
): Schema {
if (
! ($options['assumeValid'] ?? false)
Expand Down Expand Up @@ -146,7 +151,8 @@ function (string $typeName) use ($schema): Type {

return $this->extendNamedType($existingType);
},
$typeConfigDecorator
$typeConfigDecorator,
$fieldConfigDecorator
);

$this->extendTypeCache = [];
Expand Down Expand Up @@ -511,7 +517,7 @@ protected function extendFieldMap(Type $type): array
);

foreach ($extension->fields as $field) {
$newFieldMap[$field->name->value] = $this->astBuilder->buildField($field);
$newFieldMap[$field->name->value] = $this->astBuilder->buildField($field, $extension);
}
}
}
Expand Down
19 changes: 18 additions & 1 deletion tests/Utils/BuildSchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use GraphQL\Language\AST\DirectiveDefinitionNode;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\EnumTypeDefinitionNode;
use GraphQL\Language\AST\FieldDefinitionNode;
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
use GraphQL\Language\AST\Node;
Expand All @@ -26,6 +27,7 @@
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\EnumValueDefinition;
use GraphQL\Type\Definition\FieldDefinition;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\NamedType;
Expand All @@ -40,6 +42,9 @@
use GraphQL\Utils\SchemaPrinter;
use GraphQL\Validator\Rules\KnownDirectives;

/**
* @phpstan-import-type UnnamedFieldDefinitionConfig from FieldDefinition
*/
final class BuildSchemaTest extends TestCaseBase
{
use ArraySubsetAsserts;
Expand Down Expand Up @@ -1340,7 +1345,15 @@ interface Hello {
return ['description' => 'My description of ' . $node->getName()->value] + $defaultConfig;
};

$schema = BuildSchema::buildAST($doc, $typeConfigDecorator);
$fieldResolver = static fn (): string => 'OK';
$fieldConfigDecorator = static function (array $defaultConfig, FieldDefinitionNode $node) use (&$fieldResolver): array {
$defaultConfig['resolve'] = $fieldResolver;

/** @var UnnamedFieldDefinitionConfig $defaultConfig */
return $defaultConfig;
};

$schema = BuildSchema::buildAST($doc, $typeConfigDecorator, [], $fieldConfigDecorator);
$schema->getTypeMap();
self::assertSame(['Query', 'Color', 'Hello'], $decorated);

Expand All @@ -1358,6 +1371,10 @@ interface Hello {
self::assertInstanceOf(ObjectType::class, $query);
self::assertSame('My description of Query', $query->description);

self::assertSame($fieldResolver, $query->getFields()['str']->resolveFn);
self::assertSame($fieldResolver, $query->getFields()['color']->resolveFn);
self::assertSame($fieldResolver, $query->getFields()['hello']->resolveFn);

self::assertArrayHasKey(1, $calls);
[$defaultConfig, $node, $allNodesMap] = $calls[1]; // enum Color
self::assertInstanceOf(EnumTypeDefinitionNode::class, $node);
Expand Down
Loading

0 comments on commit f2748f1

Please sign in to comment.