Skip to content

Commit

Permalink
Merge pull request #690 from mll-lab/field-resolvers-docs
Browse files Browse the repository at this point in the history
Enhance the basics of resolving fields
  • Loading branch information
chrissm79 authored Mar 31, 2019
2 parents 9807cf5 + 5cf316f commit 3266644
Show file tree
Hide file tree
Showing 23 changed files with 572 additions and 223 deletions.
13 changes: 4 additions & 9 deletions docs/master/api-reference/resolvers.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,7 @@ Resolvers are always called with the same 4 arguments:
use GraphQL\Type\Definition\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;

public function resolve(
$rootValue,
array $args,
GraphQLContext $context,
ResolveInfo $resolveInfo
)
function ($rootValue, array $args, GraphQLContext $context, ResolveInfo $resolveInfo)
```

1. `$rootValue`: The result that was returned from the parent field.
Expand All @@ -27,15 +22,15 @@ Lighthouse passes in an instance of `Nuwave\Lighthouse\Schema\Context` by defaul
4. `ResolveInfo $resolveInfo`: Information about the query itself,
such as the execution state, the field name, path to the field from the root, and more.

The return value of this must fit the return type defined for the corresponding field from the schema.

## Complexity function signature

The complexity function is used to calculate a query complexity score for a field.
You can define your own complexity function with the [@complexity](../api-reference/directives.md#complexity) directive.

```php
<?php

public function complexity(int $childrenComplexity, array $args): int
function (int $childrenComplexity, array $args): int
```

1. `$childrenComplexity`: The complexity of the children of the field. In case you expect to return
Expand Down
3 changes: 3 additions & 0 deletions docs/master/getting-started/installation.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Installation

The following section teaches you how to install Lighthouse in your project.
Make sure you familiarize yourself with [the basics](../the-basics/schema.md) before diving in.

## Install via composer

```bash
Expand Down
19 changes: 19 additions & 0 deletions docs/master/guides/plugin-development.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,22 @@ You can add your custom directives to Lighthouse by listening for the [`Register

Check out [the test suite](https://github.com/nuwave/lighthouse/tree/master/tests/Integration/Events/RegisterDirectiveNamespacesTest.php)
for an example of how this works.

## Change the default resolver

The first priority when looking for a resolver is always given to `FieldResolver` directives.

After that, Lighthouse attempts to find a default resolver.

The interface [`\Nuwave\Lighthouse\Support\Contracts\ProvidesResolver`](../../../src/Support/Contracts/ProvidesResolver.php)
is expected to provide a resolver in case no resolver directive is defined for a field.

If the field is defined on the root `Query` or `Mutation` types,
Lighthouse's default implementation looks for a class with the capitalized name
of the field in the configured default location.

Non-root fields fall back to [webonyx's default resolver](http://webonyx.github.io/graphql-php/data-fetching/#default-field-resolver).
You may overwrite this by passing a `callable` to `\GraphQL\Executor\Executor::setDefaultFieldResolver`.

When the field is defined on the root `Subscription` type, the [`\Nuwave\Lighthouse\Support\Contracts\ProvidesSubscriptionResolver`](../../../src/Support/Contracts/ProvidesSubscriptionResolver.php)
interface is used instead.
246 changes: 189 additions & 57 deletions docs/master/the-basics/fields.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
# Fields

To fetch data from your GraphQL endpoint, you need to define resolvers for your fields.
Lighthouse makes this easy by providing easy to use, pre-built resolvers that work
great together with your Eloquent models.
The entrypoints to any GraphQL API are the fields of the root types `Query`, `Mutation` and `Subscription`.

## Hello World!
*Every* field has a function associated with it that is called when the field
is requested as part of a query. This function is called a **resolver**.

The following section will teach you how to define a resolver for your fields
and how you can utilize Lighthouse's built-in resolvers.

## Resolving fields

As is the tradition of our people, this section will teach you how to say "hello world!" through Lighthouse.

### Schema definition

The following schema defines a simple field called `hello` that returns a `String`.

```graphql
Expand All @@ -15,8 +22,14 @@ type Query {
}
```

You need to implement the actual resolver next. Lighthouse looks for a class with the capitalized name of the
field in `App\GraphQL\Queries` and calls its `resolve` function.
You need to implement the actual resolver next.

### Defining resolvers

By default, Lighthouse looks for a class with the capitalized name of the field in `App\GraphQL\Queries`
or `App\GraphQL\Mutations` and calls its `resolve` function with [the usual resolver arguments](../api-reference/resolvers.md#resolver-function-signature).

In this case, our field is called `hello` so we need to define our class as follows:

```php
<?php
Expand All @@ -32,6 +45,14 @@ class Hello
}
```

The easiest way to create such a class is to use the built in `artisan` commands
`lighthouse:query` and `lighthouse:mutation`. They both take a single argument:
the name of the field you want to generate.

For example, this is how you generate a class for the field `hello`:

php artisan lighthouse:query Hello

Now your schema can be queried.

```graphql
Expand All @@ -50,6 +71,165 @@ And will return the following response:
}
```

### Fields with arguments

As we learned, *every* field has a resolver function associated with it.
Just like functions, fields can take arguments to control their behaviour.

Let's construct a query that greets the user. We add a required argument `name`
that is used to construct the greeting.

```graphql
type Query {
greet(name: String!): String
}
```

A minimal implementation of the field could look something like this.
The skeleton for this class can be created using `php artisan lighthouse:query Greet`.

The second argument of the resolver function is an associative array of the
arguments that are passed to the query.

```php
<?php

namespace App\GraphQL\Queries;

class Greet
{
public function resolve($rootValue, array $args): string
{
return "Hello, {$args['name']}!";
}
}
```

We can call this query, passing a `name` of our choosing.

```graphql
{
greet(name: "Foo")
}
```

And receive a friendly greeting.

```json
{
"data": {
"greet": "Hello, Foo!"
}
}
```

If we don't want to require the user to pass an argument, we can modify our schema
and make the `name` optional and provide a default value.

```graphql
type Query {
greet(name: String = "you"): String
}
```

Now we can use our query like this:

```graphql
{
greet
}
```

```json
{
"data": {
"greet": "Hello, you!"
}
}
```

### Resolving non-root fields

As mentioned, every field in the schema has a resolver - but what
about fields that are not on one of the root types?

```graphql
type Query {
user: User!
}

type User {
id: ID!
name: String!
email: String
}
```

Let's play through what happens when the client send's the following query:

```graphql
{
user {
id
name
}
}
```

First, the resolver for `user` will be called. Let's suppose it returns an instance
of `App\Model\User`.

Next, the field sub-selection will be resolved - the two requested fields are `id` and `name`.
Since we resolved the User already in the parent field, we do not want to fetch it again
to get it's attributes.

Conveniently, the first argument of each resolver is the return value of the parent
field, in this case a User model.

A naive implementation of a resolver for `id` might look like this:

```php
<?php

use App\Models\User;

function resolveUserId(User $user): string
{
return $user->id;
}
```

Writing out each such resolver would be pretty repetitive.
We can utilize the fourth and final resolver argument `ResolveInfo`,
which will give us access to the requested field name,
to dynamically access the matching property.

```php
<?php

use App\Models\User;
use GraphQL\Type\Definition\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;

function resolveUserAttribute(User $user, array $args, GraphQLContext $context, ResolveInfo $resolveInfo)
{
return $user->{$resolveInfo->fieldName};
}
```

Fortunately, the underlying GraphQL implementation already provides [a sensible default resolver](http://webonyx.github.io/graphql-php/data-fetching/#default-field-resolver),
that plays quite nicely with the data you would typically return from
a root resolver, e.g. `Eloquent` models or associative arrays.

This means that in most cases, you will only have to provide resolvers for the
root fields and make sure they return data in the proper shape.

If you need to implement custom resolvers for fields that are not on one of the
root types `Query` or `Mutation`, you can use either the
[@field](../api-reference/directives.md#field) or [@method](../api-reference/directives.md#method) directive.

You may also [change the default resolver](../guides/plugin-development.md#change-the-default-resolver) if you need.

## Query data

Lighthouse provides many resolvers that are already built-in, so you do not have to define them yourself.
Expand Down Expand Up @@ -102,10 +282,10 @@ Will return the following result:
}
```

### Query with arguments
### Adding query constraints

You may have noticed how every field has to have a resolver function. In many ways, fields are similar to functions.
Just like functions, fields can take arguments to make them more flexible.
Lighthouse provides built-in directives to enhance your queries by giving
additional query capabilities to the client.

The following field allows you to fetch a single User by ID.

Expand Down Expand Up @@ -262,51 +442,3 @@ Lighthouse allows you to serve GraphQL subscriptions. Compared to queries and
mutations, a more elaborate setup is required.

[Read more about how to set up subscriptions](../extensions/subscriptions.md)

## Custom resolvers

Sometimes, the built-in directives just don't cut it - you need more control!
Lighthouse allows you to implement your own resolver function for fields.

By default, Lighthouse looks for a class with the capitalized name of the field in `App\GraphQL\Queries`
or `App\GraphQL\Mutations` and calls its `resolve` function with [the usual resolver arguments](../api-reference/resolvers.md#resolver-function-signature).
If you stick to that convention, you will not need to specify a directive at all.

For example, the following field:

```graphql
type Query {
latestPost: Post!
}
```

expects a class like this:

```php
<?php

namespace App\GraphQL\Queries;

use App\Post;
use GraphQL\Type\Definition\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;

class LatestPost
{
public function resolve($rootValue, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Post
{
return Post::orderBy('published_at', 'DESC')->first();
}
}
```

The easiest way to create such a class is to use the built in artisan commands
`lighthouse:query` and `lighthouse:mutation`. They both take a single argument:
the name of the field you want to generate.

For example, this is how you generate a class for the field `latestPost`:

php artisan lighthouse:query LatestPost

If you need to implement custom resolvers for fields that are not on one of the
root types `Query` or `Mutation`, you can use the [@field](../api-reference/directives.md#field) directive.
2 changes: 1 addition & 1 deletion docs/master/the-basics/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ look into the [GraphQL documentation](https://graphql.org/learn/schema/)
## Object Type

Object types define the resources of your API and are closely related to Eloquent models.
They must have a unique name and have a set of fields.
They must have a unique name and contain a set of fields.

```graphql
type User {
Expand Down
10 changes: 5 additions & 5 deletions src/Defer/DeferrableDirective.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@ public function name(): string
*/
public function handleField(FieldValue $value, Closure $next): FieldValue
{
$resolver = $value->getResolver();
$previousResolver = $value->getResolver();
$fieldType = $value->getField()->type;

$value->setResolver(
function ($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) use ($resolver, $fieldType) {
$wrappedResolver = function () use ($resolver, $root, $args, $context, $resolveInfo) {
return $resolver($root, $args, $context, $resolveInfo);
function ($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) use ($previousResolver, $fieldType) {
$wrappedResolver = function () use ($previousResolver, $root, $args, $context, $resolveInfo) {
return $previousResolver($root, $args, $context, $resolveInfo);
};
$path = implode('.', $resolveInfo->path);

Expand All @@ -67,7 +67,7 @@ function ($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo)

return $this->defer->isStreaming()
? $this->defer->findOrResolve($wrappedResolver, $path)
: $resolver($root, $args, $context, $resolveInfo);
: $previousResolver($root, $args, $context, $resolveInfo);
}
);

Expand Down
Loading

0 comments on commit 3266644

Please sign in to comment.