Skip to content

Commit 12d0a33

Browse files
committed
Add 'Deferred type loading' to documentation
1 parent e0fa0c7 commit 12d0a33

File tree

2 files changed

+111
-1
lines changed

2 files changed

+111
-1
lines changed

docs/todo.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ This library is still work in progress:
1111
- ~~GraphQL interfaces, inheritance~~
1212
- ~~Interfaces extending interfaces (see https://graphql.org/learn/schema/#interface-types)~~
1313
- ~~Union types (see https://graphql.org/learn/schema/#union-types)~~
14-
- [WIP] Deferred loaders (N+1 problem) (see https://webonyx.github.io/graphql-php/data-fetching/#solving-n1-problem)
14+
- ~~Deferred loaders (N+1 problem) (see https://webonyx.github.io/graphql-php/data-fetching/#solving-n1-problem)~~
1515
- Subscriptions
1616
- Directives (see https://graphql.org/learn/schema/#directives)

docs/usage.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
- [Attributes](#attributes)
44
- [Union types](#union-types)
55
- [Connections (Pagination)](#connections-pagination)
6+
- [Deferred type loading (Solving N+1 problem)](#deferred-type-loading-solving-n1-problem)
67

78
📌 At a minimum, you need to define a query and a mutation to build a valid schema.
89

@@ -707,3 +708,112 @@ You can also define additional custom input arguments if needed.
707708
Each 'node' in a connection must have a `#[Cursor]` attribute. You can add this on a **property** or a **method**. This defines the cursor output for each "edge."
708709

709710
💡 **Note:** If you **don't** define a `#[Cursor]`, the cursor will always be `null` when querying.
711+
712+
## Deferred type loading (Solving N+1 problem)
713+
714+
When working with a nested GraphQL model, the famous N+1 problem can become an issue.
715+
As a solution, Webonyx/graphql-php has introduced so-called 'Deferred type loading'
716+
717+
For more details, check out:
718+
- [Phabricator: Performance: N+1 Query Problem](https://secure.phabricator.com/book/phabcontrib/article/n_plus_one/)
719+
- [Webonyx: Solving N+1 Problem](https://webonyx.github.io/graphql-php/data-fetching/#solving-n1-problem)
720+
721+
To use 'Deferred type loading' in _GraphQL Attribute Schema_, for each type, a custom `DeferredTypeLoader` needs to be created.
722+
This `DeferredTypeLoader` can then be configured in `#[Query]`, `#[Mutation]` or `#[Field]`.
723+
724+
### Example
725+
Create a custom `DeferredTypeLoader` for GraphQL type `SomeType`:
726+
727+
```php
728+
use Jerowork\GraphqlAttributeSchema\Type\Loader\DeferredTypeLoader;
729+
730+
final readonly class SomeTypeLoader implements DeferredTypeLoader
731+
{
732+
public function __construct(
733+
private SomeRepository $repository,
734+
) {}
735+
736+
public function load(array $references) : array
737+
{
738+
return array_map(
739+
fn($item) => new DeferredType($item->getId(), new SomeType($item)),
740+
$this->repository->findByIds($references),
741+
);
742+
}
743+
}
744+
```
745+
746+
Configure `#[Query]` or `#[Mutation]`:
747+
748+
```php
749+
use Jerowork\GraphqlAttributeSchema\Attribute\Mutation;
750+
use Jerowork\GraphqlAttributeSchema\Attribute\Query;
751+
752+
final readonly class SomeQuery
753+
{
754+
#[Query(type: SomeType::class, deferredTypeLoader: SomeTypeLoader::class)]
755+
public function someQuery(string $id): string
756+
{
757+
return $id;
758+
}
759+
}
760+
761+
final readonly class SomeMutation
762+
{
763+
#[Mutation(type: SomeType::class, deferredTypeLoader: SomeTypeLoader::class)]
764+
public function someMutation(string $id): string
765+
{
766+
return $id;
767+
}
768+
}
769+
```
770+
771+
Or configure `#[Field]` on property or method:
772+
773+
```php
774+
use Jerowork\GraphqlAttributeSchema\Attribute\Field;
775+
use Jerowork\GraphqlAttributeSchema\Attribute\Type;
776+
777+
#[Type]
778+
final readonly class SomeResponseType
779+
{
780+
// Configure on property
781+
public function __construct(
782+
#[Field(name: 'some', type: SomeType::class, deferredTypeLoader: SomeTypeLoader::class)]
783+
public string $id,
784+
) {}
785+
786+
// Or configure on method
787+
#[Field(type: SomeType::class, deferredTypeLoader: SomeTypeLoader::class)]
788+
public function getSome(string $id): string
789+
{
790+
return $id;
791+
}
792+
}
793+
```
794+
795+
### About `DeferredTypeLoader`
796+
797+
A custom implementation needs to implement `DeferredTypeLoader`. This interface defines one method: `load`.
798+
799+
Input `$references` is a list of type references (identifiers), requested by `#[Query]`, `#[Mutation]` or `#[Field]`.
800+
801+
Output is a list of `DeferredType`, containing the reference of each type and the GraphQL type itself.
802+
803+
Each `DeferredTypeLoader` implementation must be retrievable from the (PSR-11) container via `get()`.
804+
For Symfony users, make sure they're set to public (e.g., with `#[Autoconfigure(public: true)]`).
805+
This allows injection of database repositories and other necessary services.
806+
807+
### Configuring in `#[Query]`, `#[Mutation]` or `#[Field]`
808+
809+
By configuring the `deferredTypeLoader` option on `#[Query]`, `#[Mutation]` or `#[Field]`,
810+
the property or method will be converted to a 'Deferred type loading' field.
811+
812+
Each 'Deferred type loading' field should **only** return the identifier (or 'reference').
813+
It can also be a list of references. This reference(s) will automatically be added to the list `$references` in the configured `DeferredTypeLoader`.
814+
815+
The returning reference should be of type: integer, string, `Stringable`, or a list of the mentioned.
816+
817+
As the actual loading now is handled by the `DeferredTypeLoader`, no loading is required in this 'Deferred type loading' field anymore.
818+
819+
**Note:** In order to configure the GraphQL schema with the correct GraphQL type. Setting the `type` option on `#[Query]`, `#[Mutation]` or `#[Field]` is required.

0 commit comments

Comments
 (0)