Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow lazy root types (query, mutation, subscription) #1418

Merged
merged 18 commits into from
Aug 4, 2023
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ You can find and compare releases at the [GitHub release page](https://github.co

## Unreleased

### Added

- Allow lazy root types `query`, `mutation`, `subscription` https://github.com/webonyx/graphql-php/pull/1418

## v15.5.3

### Fixed
Expand Down
67 changes: 64 additions & 3 deletions docs/class-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -535,12 +535,13 @@ Usage example:

@see Type, NamedType

@phpstan-type MaybeLazyObjectType ObjectType|(callable(): ObjectType)|null
@phpstan-type TypeLoader callable(string $typeName): ((Type&NamedType)|null)
@phpstan-type Types iterable<Type&NamedType>|(callable(): iterable<Type&NamedType>)
@phpstan-type SchemaConfigOptions array{
query?: ObjectType|null,
mutation?: ObjectType|null,
subscription?: ObjectType|null,
query?: MaybeLazyObjectType,
mutation?: MaybeLazyObjectType,
subscription?: MaybeLazyObjectType,
types?: Types|null,
directives?: array<Directive>|null,
typeLoader?: TypeLoader|null,
Expand All @@ -563,6 +564,66 @@ extensionASTNodes?: array<SchemaExtensionNode>|null,
static function create(array $options = []): self
```

```php
/**
* @return MaybeLazyObjectType
*
* @api
*/
function getQuery()
```

```php
/**
* @param MaybeLazyObjectType $query
*
* @throws InvariantViolation
*
* @api
*/
function setQuery($query): self
```

```php
/**
* @return MaybeLazyObjectType
*
* @api
*/
function getMutation()
```

```php
/**
* @param MaybeLazyObjectType $mutation
*
* @throws InvariantViolation
*
* @api
*/
function setMutation($mutation): self
```

```php
/**
* @return MaybeLazyObjectType
*
* @api
*/
function getSubscription()
```

```php
/**
* @param MaybeLazyObjectType $subscription
*
* @throws InvariantViolation
*
* @api
*/
function setSubscription($subscription): self
```

```php
/**
* @return array|callable
Expand Down
38 changes: 34 additions & 4 deletions src/Type/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ public function getTypeMap(): array
TypeInfo::extractTypes($type, $allReferencedTypes);
}

foreach ([$this->config->query, $this->config->mutation, $this->config->subscription] as $rootType) {
foreach ([$this->getQueryType(), $this->getMutationType(), $this->getSubscriptionType()] as $rootType) {
if ($rootType instanceof ObjectType) {
TypeInfo::extractTypes($rootType, $allReferencedTypes);
}
Expand Down Expand Up @@ -214,7 +214,17 @@ public function getOperationType(string $operation): ?ObjectType
*/
public function getQueryType(): ?ObjectType
{
return $this->config->query;
$query = $this->config->query;

if ($query === null) {
return null;
}

if (is_callable($query)) {
return $this->config->query = ($query)();
spawnia marked this conversation as resolved.
Show resolved Hide resolved
}

return $query;
}

/**
Expand All @@ -224,7 +234,17 @@ public function getQueryType(): ?ObjectType
*/
public function getMutationType(): ?ObjectType
{
return $this->config->mutation;
$mutation = $this->config->mutation;

if ($mutation === null) {
return null;
}

if (is_callable($mutation)) {
return $this->config->mutation = ($mutation)();
spawnia marked this conversation as resolved.
Show resolved Hide resolved
}

return $mutation;
}

/**
Expand All @@ -234,7 +254,17 @@ public function getMutationType(): ?ObjectType
*/
public function getSubscriptionType(): ?ObjectType
{
return $this->config->subscription;
$subscription = $this->config->subscription;

if ($subscription === null) {
return null;
}

if (is_callable($subscription)) {
return $this->config->subscription = ($subscription)();
spawnia marked this conversation as resolved.
Show resolved Hide resolved
}

return $subscription;
}

/** @api */
Expand Down
92 changes: 74 additions & 18 deletions src/Type/SchemaConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace GraphQL\Type;

use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\SchemaDefinitionNode;
use GraphQL\Language\AST\SchemaExtensionNode;
use GraphQL\Type\Definition\Directive;
Expand All @@ -25,12 +26,13 @@
*
* @see Type, NamedType
*
* @phpstan-type MaybeLazyObjectType ObjectType|(callable(): ObjectType)|null
* @phpstan-type TypeLoader callable(string $typeName): ((Type&NamedType)|null)
* @phpstan-type Types iterable<Type&NamedType>|(callable(): iterable<Type&NamedType>)
* @phpstan-type SchemaConfigOptions array{
* query?: ObjectType|null,
* mutation?: ObjectType|null,
* subscription?: ObjectType|null,
* query?: MaybeLazyObjectType,
* mutation?: MaybeLazyObjectType,
* subscription?: MaybeLazyObjectType,
* types?: Types|null,
* directives?: array<Directive>|null,
* typeLoader?: TypeLoader|null,
Expand All @@ -41,11 +43,14 @@
*/
class SchemaConfig
{
public ?ObjectType $query = null;
/** @var MaybeLazyObjectType */
public $query;

public ?ObjectType $mutation = null;
/** @var MaybeLazyObjectType */
public $mutation;

public ?ObjectType $subscription = null;
/** @var MaybeLazyObjectType */
public $subscription;

/**
* @var iterable|callable
Expand Down Expand Up @@ -124,43 +129,76 @@ public static function create(array $options = []): self
return $config;
}

/** @api */
public function getQuery(): ?ObjectType
/**
* @return MaybeLazyObjectType
*
* @api
*/
public function getQuery()
{
return $this->query;
}

/** @api */
public function setQuery(?ObjectType $query): self
/**
* @param MaybeLazyObjectType $query
*
* @throws InvariantViolation
*
* @api
*/
public function setQuery($query): self
{
$this->assertMaybeLazyObjectType($query);
$this->query = $query;

return $this;
}

/** @api */
public function getMutation(): ?ObjectType
/**
* @return MaybeLazyObjectType
*
* @api
*/
public function getMutation()
{
return $this->mutation;
}

/** @api */
public function setMutation(?ObjectType $mutation): self
/**
* @param MaybeLazyObjectType $mutation
*
* @throws InvariantViolation
*
* @api
*/
public function setMutation($mutation): self
{
$this->assertMaybeLazyObjectType($mutation);
$this->mutation = $mutation;

return $this;
}

/** @api */
public function getSubscription(): ?ObjectType
/**
* @return MaybeLazyObjectType
*
* @api
*/
public function getSubscription()
{
return $this->subscription;
}

/** @api */
public function setSubscription(?ObjectType $subscription): self
/**
* @param MaybeLazyObjectType $subscription
*
* @throws InvariantViolation
*
* @api
*/
public function setSubscription($subscription): self
{
$this->assertMaybeLazyObjectType($subscription);
$this->subscription = $subscription;

return $this;
Expand Down Expand Up @@ -275,4 +313,22 @@ public function setExtensionASTNodes(array $extensionASTNodes): self

return $this;
}

/**
* @param mixed $maybeLazyObjectType Should be MaybeLazyObjectType
*
* @throws InvariantViolation
*/
protected function assertMaybeLazyObjectType($maybeLazyObjectType): void
{
if ($maybeLazyObjectType instanceof ObjectType || is_callable($maybeLazyObjectType) || is_null($maybeLazyObjectType)) {
return;
}

$notMaybeLazyObjectType = is_object($maybeLazyObjectType)
? get_class($maybeLazyObjectType)
: gettype($maybeLazyObjectType);
$objectTypeClass = ObjectType::class;
throw new InvariantViolation("Expected instanceof {$objectTypeClass}, a callable that returns such an instance, or null, got: {$notMaybeLazyObjectType}.");
}
}
4 changes: 2 additions & 2 deletions src/Utils/BuildClientSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ public function __construct(array $introspectionQuery, array $options = [])
* the "errors" field of a server response before calling this function.
*
* @param array<string, mixed> $introspectionQuery
* @param array<string, bool> $options
* @param array<string, bool> $options
*
* @phpstan-param Options $options
* @phpstan-param Options $options
*
* @api
*
Expand Down
26 changes: 26 additions & 0 deletions tests/Type/LazyDefinitionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -182,4 +182,30 @@ public function testReturningFieldsUsingYield(): void
self::assertSame($field->name, 'width');
self::assertInstanceOf(IntType::class, $field->getType());
}

public function testLazyRootTypes(): void
{
$query = new ObjectType([
'name' => 'Query',
'fields' => [],
]);
$mutation = new ObjectType([
'name' => 'Mutation',
'fields' => [],
]);
$subscription = new ObjectType([
'name' => 'Subscription',
'fields' => [],
]);

$schema = new Schema([
'query' => fn () => $query,
'mutation' => fn () => $mutation,
'subscription' => fn () => $subscription,
]);

self::assertSame($schema->getQueryType(), $query);
self::assertSame($schema->getMutationType(), $mutation);
self::assertSame($schema->getSubscriptionType(), $subscription);
}
}