2020-10-19 23:56:17 +02:00
---
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]
2022-06-28 06:27:06 +02:00
[info]
You are viewing docs for silverstripe/graphql 4.x.
If you are using 3.x, documentation can be found
[in the github repository ](https://github.com/silverstripe/silverstripe-graphql/tree/3 )
[/info]
2020-10-19 23:56:17 +02:00
## The resolver discovery pattern
2022-06-08 07:23:48 +02:00
When you define a query, mutation, or any other field on a type, you can opt out of providing
2020-10-19 23:56:17 +02:00
an explicit resolver and allow the system to discover one for you based on naming convention.
2022-06-08 07:23:48 +02:00
Let's start by registering a resolver class where we can define a bunch of these methods.
You can register as many classes as makes sense - and each resolver class can have multiple
resolver methods.
2020-10-19 23:56:17 +02:00
2020-12-01 09:27:52 +01:00
**app/_graphql/config.yml**
2020-10-19 23:56:17 +02:00
```yaml
2020-12-01 09:27:52 +01:00
resolvers:
- MyProject\Resolvers\MyResolvers
2020-10-19 23:56:17 +02:00
```
2020-12-01 09:27:52 +01:00
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
2022-06-08 07:23:48 +02:00
a method name given a class name, type name, and [`Field` ](api:SilverStripe\GraphQL\Schema\Field\Field ) instance.
2020-10-19 23:56:17 +02:00
```php
2022-06-08 07:23:48 +02:00
class Strategy
{
public static function getResolverMethod(string $className, ?string $typeName = null, ?Field $field = null): ?string
{
// strategy logic here
}
}
2020-10-19 23:56:17 +02:00
```
2020-12-01 09:27:52 +01:00
#### The default resolver strategy
2022-06-08 07:23:48 +02:00
By default, all schemas use [`DefaultResolverStrategy::getResolverMethod()` ](api:SilverStripe\GraphQL\Schema\Resolver\DefaultResolverStrategy::getResolverMethod( ))
2020-12-01 09:27:52 +01:00
to discover resolver functions. The logic works like this:
* Does `resolve<TypeName><FieldName>` exist?
2022-06-08 07:23:48 +02:00
* Yes? Return that method name
2020-12-01 09:27:52 +01:00
* No? Continue
* Does `resolve<TypeName>` exist?
2022-06-08 07:23:48 +02:00
* Yes? Return that method name
2020-12-01 09:27:52 +01:00
* No? Continue
* Does `resolve<FieldName>` exist?
2022-06-08 07:23:48 +02:00
* Yes? Return that method name
2020-12-01 09:27:52 +01:00
* No? Continue
* Does `resolve` exist?
2022-06-08 07:23:48 +02:00
* Yes? Return that method name
2020-12-01 09:27:52 +01:00
* No? Return null. This resolver cannot be discovered
2020-10-19 23:56:17 +02:00
Let's look at our query again:
```graphql
query {
readCountries {
name
}
}
```
2022-06-08 07:23:48 +02:00
Imagine we have two classes registered under `resolvers` - `ClassA` and `ClassB`
**app/_graphql/config.yml**
```yaml
resolvers:
- ClassA
- ClassB
```
2020-10-19 23:56:17 +02:00
2022-06-08 07:23:48 +02:00
The `DefaultResolverStrategy` will check for methods in this order:
2020-10-19 23:56:17 +02:00
2022-06-08 07:23:48 +02:00
* `ClassA::resolveCountryName()`
* `ClassA::resolveCountry()`
* `ClassA::resolveName()`
* `ClassA::resolve()`
* `ClassB::resolveCountryName()`
* `ClassB::resolveCountry()`
* `ClassB::resolveName()`
* `ClassB::resolve()`
* Return `null` .
2020-12-01 09:27:52 +01:00
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' ]
```
2020-10-19 23:56:17 +02:00
Let's add a resolver method to our resolver provider:
**app/src/Resolvers/MyResolvers.php**
```php
2022-06-08 07:23:48 +02:00
namespace MyApp\Resolvers;
2020-10-19 23:56:17 +02:00
2020-12-01 09:27:52 +01:00
class MyResolvers
2020-10-19 23:56:17 +02:00
{
public static function resolveReadCountries()
{
$results = [];
$countries = Injector::inst()->get(Locales::class)->getCountries();
foreach ($countries as $code => $name) {
$results[] = [
'code' => $code,
'name' => $name
];
}
return $results;
}
}
```
2022-06-08 07:23:48 +02:00
Now that we're using logic to discover our resolver, we can remove our resolver method declarations from the individual
queries and instead just register the resolver class.
2020-10-19 23:56:17 +02:00
2022-06-08 07:23:48 +02:00
**app/_graphql/config.yml**
```yaml
resolvers:
- MyApp\Resolvers\MyResolvers
```
2020-10-19 23:56:17 +02:00
**app/_graphql/schema.yml**
```yml
queries:
readCountries: '[Country]'
```
2022-06-08 07:23:48 +02:00
Re-run the schema build, with a flush (because we created a new PHP class), and let's go!
2020-10-19 23:56:17 +02:00
2022-06-08 07:23:48 +02:00
`vendor/bin/sake dev/graphql/build schema=default flush=1`
2020-10-19 23:56:17 +02:00
### 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
2022-06-08 07:23:48 +02:00
ground between the rigor of hard coding everything at a query level, and the opacity of discovery logic.
2020-10-19 23:56:17 +02:00
**app/_graphql/schema.yml**
```yml
types:
Country:
fields:
name: String
code: String
2022-06-08 07:23:48 +02:00
fieldResolver: [ 'MyProject\MyResolver', 'resolveCountryFields' ]
2020-10-19 23:56:17 +02:00
```
2022-06-08 07:23:48 +02:00
In this case the registered resolver method will be used to resolve any number of fields.
You'll need to do explicit checks for the field name in your resolver to make this work.
2020-10-19 23:56:17 +02:00
```php
2022-06-08 07:23:48 +02:00
public static function resolveCountryFields($obj, $args, $context, ResolveInfo $info)
2020-10-19 23:56:17 +02:00
{
$fieldName = $info->fieldName;
if ($fieldName === 'image') {
return $obj->getImage()->getURL();
}
// .. etc
}
```
### Further reading
[CHILDREN]