mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-09-19 16:06:32 +02:00
162 lines
4.3 KiB
Markdown
162 lines
4.3 KiB
Markdown
---
|
|
title: The resolver discovery pattern
|
|
summary: How you can opt out of mapping fields to resolvers by adhering to naming conventions
|
|
---
|
|
|
|
# Working with generic types
|
|
|
|
[CHILDREN asList]
|
|
|
|
[alert]
|
|
You are viewing docs for a pre-release version of silverstripe/graphql (4.x).
|
|
Help us improve it by joining #graphql on the [Community Slack](https://www.silverstripe.org/blog/community-slack-channel/),
|
|
and report any issues at [github.com/silverstripe/silverstripe-graphql](https://github.com/silverstripe/silverstripe-graphql).
|
|
Docs for the current stable version (3.x) can be found
|
|
[here](https://github.com/silverstripe/silverstripe-graphql/tree/3)
|
|
[/alert]
|
|
|
|
## The resolver discovery pattern
|
|
|
|
When you define a query mutation, or any other field on a type, you can opt out of providing
|
|
an explicit resolver and allow the system to discover one for you based on naming convention.
|
|
|
|
Let's start by registering a resolver class(es) where we can define a bunch of these functions.
|
|
|
|
**app/_graphql/config.yml**
|
|
```yaml
|
|
resolvers:
|
|
- MyProject\Resolvers\MyResolvers
|
|
```
|
|
|
|
What we're registering here is a generic class that should contain one or more static functions that resolve one
|
|
or many fields. How those functions will be discovered relies on the _resolver strategy_.
|
|
|
|
### Resolver strategy
|
|
|
|
Each schema config accepts a `resolverStrategy` property. This should map to a callable that will return
|
|
a method name given a class name, type name, and `Field` instance.
|
|
|
|
```php
|
|
public static function getResolverMethod(string $className, ?string $typeName = null, ?Field $field = null): ?string;
|
|
```
|
|
|
|
#### The default resolver strategy
|
|
|
|
By default, all schemas use `SilverStripe\GraphQL\Schema\Resolver\DefaultResolverStrategy::getResolerMethod`
|
|
to discover resolver functions. The logic works like this:
|
|
|
|
* Does `resolve<TypeName><FieldName>` exist?
|
|
* Yes? Invoke
|
|
* No? Continue
|
|
* Does `resolve<TypeName>` exist?
|
|
* Yes? Invoke
|
|
* No? Continue
|
|
* Does `resolve<FieldName>` exist?
|
|
* Yes? Invoke
|
|
* No? Continue
|
|
* Does `resolve` exist?
|
|
* Yes? Invoke
|
|
* No? Return null. This resolver cannot be discovered
|
|
|
|
|
|
Let's look at our query again:
|
|
|
|
```graphql
|
|
query {
|
|
readCountries {
|
|
name
|
|
}
|
|
}
|
|
```
|
|
|
|
Imagine we have two classes registered under `resolvers` -- `ClassA` and `ClassB`
|
|
|
|
The logic will go like this:
|
|
|
|
* `ClassA::resolveCountryName`
|
|
* `ClassA::resolveCountry`
|
|
* `ClassA::resolveName`
|
|
* `ClassA::resolve`
|
|
* `ClassB::resolveCountryName`
|
|
* `ClassB::resolveCountry`
|
|
* `ClassB::resolveName`
|
|
* `ClassB::resolve`
|
|
* Return null.
|
|
|
|
You can implement whatever strategy you like in your schema. Just register it to `resolverStrategy` in the config.
|
|
|
|
**app/_graphql/config.yml**
|
|
```yaml
|
|
resolverStrategy: [ 'MyApp\Resolvers\Strategy', 'getResolverMethod' ]
|
|
```
|
|
|
|
Let's add a resolver method to our resolver provider:
|
|
|
|
**app/src/Resolvers/MyResolvers.php**
|
|
```php
|
|
|
|
class MyResolvers
|
|
{
|
|
public static function resolveReadCountries()
|
|
{
|
|
$results = [];
|
|
$countries = Injector::inst()->get(Locales::class)->getCountries();
|
|
foreach ($countries as $code => $name) {
|
|
$results[] = [
|
|
'code' => $code,
|
|
'name' => $name
|
|
];
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
}
|
|
```
|
|
|
|
|
|
Now that we're using logic to discover our resolver, we can clean up the config a bit.
|
|
|
|
**app/_graphql/schema.yml**
|
|
```yml
|
|
queries:
|
|
readCountries: '[Country]'
|
|
```
|
|
|
|
Re-run the schema build, with a flush, and let's go!
|
|
|
|
`$ vendor/bin/sake dev/graphql/build schema=default flush=1`
|
|
|
|
|
|
### Field resolvers
|
|
|
|
A less magical approach to resolver discovery is defining a `fieldResolver` property on your
|
|
types. This is a generic handler for all fields on a given type and can be a nice middle
|
|
ground between the rigor of hard coding everything and the opacity of discovery logic.
|
|
|
|
**app/_graphql/schema.yml**
|
|
```yml
|
|
types:
|
|
Country:
|
|
fields:
|
|
name: String
|
|
code: String
|
|
fieldResolver: [ 'MyProject\MyResolver', 'resolveCountryField' ]
|
|
```
|
|
|
|
You'll need to do explicit checks for the `fieldName` in your resolver to make this work.
|
|
|
|
```php
|
|
public static function resolveCountryField($obj, $args, $context, ResolveInfo $info)
|
|
{
|
|
$fieldName = $info->fieldName;
|
|
if ($fieldName === 'image') {
|
|
return $obj->getImage()->getURL();
|
|
}
|
|
// .. etc
|
|
}
|
|
```
|
|
|
|
### Further reading
|
|
|
|
[CHILDREN]
|