silverstripe-framework/docs/en/02_Developer_Guides/19_GraphQL/03_working_with_generic_types/02_building_a_custom_query.md

178 lines
5.4 KiB
Markdown
Raw Normal View History

WIP: Add new graphql 4 docs (#9652) * DOCS: Add new graphql 4 docs * Reorganise docs * Docs done * Basic graphql index page * TOC for getting started * show folders on graphql index page * Add middleware note * Docs update * Update docs to reflect flushless schema * Docs updates * Docs for getByLink * Query caching docs * Docs on nested operations * update docs for new graphql dev admin * Docs for configurable operations * Replace readSiteTrees with readPages * Schema defaults docs * Docs for inherited plugins * Docs for customising * * Docs for field whitelisting * Change whitelist word * New docs on modelConfig * Document dev/build extension * Document default/global plugins * Document new input type fields config * Apply suggestions from code review Co-authored-by: Andre Kiste <bergice@users.noreply.github.com> * Note about when procedural schema gets built * Fix link * Apply suggestions from code review Co-authored-by: Andre Kiste <bergice@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Andre Kiste <bergice@users.noreply.github.com> * DOCS Note about plugins in custom queries * DOCS Note about filter and custom resolvers * DOCS Note about canview paging * DOCS Updated guidance on _extend See https://github.com/silverstripe/silverstripe-graphql/issues/296 * Apply suggestions from code review Co-authored-by: Andre Kiste <bergice@users.noreply.github.com> * DOCS Pre-release warning Co-authored-by: Ingo Schommer <ingo@silverstripe.com> Co-authored-by: Andre Kiste <bergice@users.noreply.github.com> Co-authored-by: Ingo Schommer <me@chillu.com>
2020-10-19 23:56:17 +02:00
---
title: Building a custom query
summary: Add a custom query for any type of data
---
# 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]
## Building a custom query
We've now defined the shape of our data, now we need to build a way to access it. For this,
we'll need a query. Let's add one to the `queries` section of our config.
*app/_graphql/schema.yml*
```yaml
types:
Country:
fields:
code: String!
name: String!
queries:
readCountries: '[Country]'
```
Now we have a query that will return all the countries. In order to make this work, we'll
need a **resolver**. For this, we're going to have to break out of the configuration layer
and write some code.
**app/src/Resolvers/MyResolver.php**
```php
class MyResolver
{
public static function resolveCountries(): array
{
$results = [];
$countries = Injector::inst()->get(Locales::class)->getCountries();
foreach ($countries as $code => $name) {
$results[] = [
'code' => $code,
'name' => $name
];
}
return $results;
}
}
```
Resolvers are pretty loosely defined, and don't have to adhere to any specific contract
other than that they **must be static methods**. You'll see why when we add it to the configuration:
*app/_graphql/schema.yml
```yaml
types:
Country:
fields:
code: String!
name: String!
queries:
readCountries:
type: '[Country]'
resolver: [ 'MyResolver', 'resolveCountries' ]
```
Now, we just have to build the schema:
`$ vendor/bin/sake dev/graphql/build schema=default`
Let's test this out in our GraphQL IDE. If you have the [graphql-devtools](https://github.com/silverstripe/silverstripe-graphql-devtools) module installed, just open it up and set it to the `/graphql` endpoint.
As you start typing, it should autocomplete for you.
Here's our query:
```graphql
query {
readCountries {
name
code
}
}
```
And the expected response:
```json
{
"data": {
"readCountries": [
{
"name": "Afghanistan",
"code": "af"
},
{
"name": "Åland Islands",
"code": "ax"
},
"... etc"
]
}
}
```
[notice]
Keep in mind that [plugins](../02_working_with_dataobjects/02_query_plugins.md)
don't apply in this context. Most importantly, this means you need to
implement your own `canView()` checks.
[/notice]
## Resolver Method Arguments
A resolver is executed in a particular query context,
which is passed into the method as arguments.
* `$value`: An optional `mixed` value of the parent in your data graph.
Defaults to `null` on the root level, but can be useful to retrieve the object
when writing field-specific resolvers (see [Resolver Discovery](resolver_discovery))
* `$args`: An array of optional arguments for this field (which is different from the [Query Variables](https://graphql.org/learn/queries/#variables))
* `$context`: An arbitrary array which holds information shared between resolvers.
Use implementors of `SilverStripe\GraphQL\Schema\Interfaces\ContextProvider` to get and set
data, rather than relying on the array keys directly.
* `$info`: Data structure containing useful information for the resolving process (e.g. the field name).
See [Fetching Data](http://webonyx.github.io/graphql-php/data-fetching/) in the underlying PHP library for details.
## Using Context Providers
The `$context` array can be useful to get access to the HTTP request,
retrieve the current member, or find out details about the schema.
You can use it through implementors of the `SilverStripe\GraphQL\Schema\Interfaces\ContextProvider` interface.
In the example below, we'll demonstrate how you could limit viewing the country code to
users with ADMIN permissions.
**app/src/Resolvers/MyResolver.php**
```php
use GraphQL\Type\Definition\ResolveInfo;
use SilverStripe\GraphQL\QueryHandler\UserContextProvider;
use SilverStripe\Security\Permission;
class MyResolver
{
public static function resolveCountries($value = null, array $args = [], array $context = [], ?ResolveInfo $info = null): array
{
$member = UserContextProvider::get($context);
$canViewCode = ($member && Permission::checkMember($member, 'ADMIN'));
$results = [];
$countries = Injector::inst()->get(Locales::class)->getCountries();
foreach ($countries as $code => $name) {
$results[] = [
'code' => $canViewCode ? $code : '',
'name' => $name
];
}
return $results;
}
}
```
## Resolver Discovery
WIP: Add new graphql 4 docs (#9652) * DOCS: Add new graphql 4 docs * Reorganise docs * Docs done * Basic graphql index page * TOC for getting started * show folders on graphql index page * Add middleware note * Docs update * Update docs to reflect flushless schema * Docs updates * Docs for getByLink * Query caching docs * Docs on nested operations * update docs for new graphql dev admin * Docs for configurable operations * Replace readSiteTrees with readPages * Schema defaults docs * Docs for inherited plugins * Docs for customising * * Docs for field whitelisting * Change whitelist word * New docs on modelConfig * Document dev/build extension * Document default/global plugins * Document new input type fields config * Apply suggestions from code review Co-authored-by: Andre Kiste <bergice@users.noreply.github.com> * Note about when procedural schema gets built * Fix link * Apply suggestions from code review Co-authored-by: Andre Kiste <bergice@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Andre Kiste <bergice@users.noreply.github.com> * DOCS Note about plugins in custom queries * DOCS Note about filter and custom resolvers * DOCS Note about canview paging * DOCS Updated guidance on _extend See https://github.com/silverstripe/silverstripe-graphql/issues/296 * Apply suggestions from code review Co-authored-by: Andre Kiste <bergice@users.noreply.github.com> * DOCS Pre-release warning Co-authored-by: Ingo Schommer <ingo@silverstripe.com> Co-authored-by: Andre Kiste <bergice@users.noreply.github.com> Co-authored-by: Ingo Schommer <me@chillu.com>
2020-10-19 23:56:17 +02:00
This is great, but as we write more and more queries for types with more and more fields,
it's going to get awfully laborious mapping all these resolvers. Let's clean this up a bit by
adding a bit of convention over configuration, and save ourselves a lot of time to boot. We can do
that using the [resolver discovery pattern](resolver_discovery).
### Further reading
[CHILDREN]