Skip to content

Commit b42e147

Browse files
joaogarinfubhy
authored andcommitted
docs(composable): Document composable schemas and union/interface type resolvers. (#847)
1 parent 3061acc commit b42e147

5 files changed

Lines changed: 159 additions & 77 deletions

File tree

doc/SUMMARY.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,7 @@
2323
## Mutations
2424

2525
* [Mutations](mutations/README.md)
26+
27+
## Advanced
28+
29+
* [Composable schemas](advanced/composable-schemas.md)

doc/advanced/composable-schemas.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Splitting up the schema over multiple modules with composable schemas
2+
3+
It is possible to split up the schema into separate parts so that you can enable certain functionality that is for example tied to a particular module only when that module is enabled.
4+
5+
You can find a complete example [here](https://github.com/drupal-graphql/graphql/tree/8.x-4.x/examples) on how this can be done.
6+
7+
If you have a complex system where certain modules are sometimes enabled / disabled and you want to make sure the API matches the functionality provided by those modules and also disable the API's that correspond to those modules when the modules are disabled splitting schemas into chunks is a good idea. This will keep the system organized and easy to read and look at.
8+
9+
## Register a new Schema Extension
10+
11+
A new schema extension can be inserted inside a new module so that it can extend a given schema (in the example bellow the schema `example`) with new fields and types. The `schema` key will tell which schema to attach these types to and the `id` key will be used to tell which files to pick up from when generating the schema.
12+
13+
```php
14+
<?php
15+
namespace Drupal\graphql_examples\Plugin\GraphQL\SchemaExtension;
16+
use Drupal\graphql\GraphQL\ResolverBuilder;
17+
use Drupal\graphql\GraphQL\ResolverRegistryInterface;
18+
use Drupal\graphql\Plugin\GraphQL\SchemaExtension\SdlSchemaExtensionPluginBase;
19+
/**
20+
* @SchemaExtension(
21+
* id = "example_extension",
22+
* name = "Example extension",
23+
* description = "A simple extension that adds node related fields.",
24+
* schema = "example"
25+
* )
26+
*/
27+
class ExampleSchemaExtension extends SdlSchemaExtensionPluginBase {
28+
}
29+
```
30+
31+
## Add new types and fields
32+
33+
To add new types and fields to the schema create a file inside `/graphql/example_extension.base.graphqls` (as seen [here](https://github.com/drupal-graphql/graphql/blob/8.x-4.x/examples/graphql/example_extension.base.graphqls)) with the new types :
34+
35+
```
36+
type Page {
37+
id: Int!
38+
title: String
39+
}
40+
```
41+
42+
In this case we are creating a new type `Page`.
43+
44+
## Change existing types or fields
45+
46+
Normally a new module when enabled should also change or add more fields to an existing type. In that case create new file `/graphql/example_extension.extension.graphqls` as seen [here](https://github.com/drupal-graphql/graphql/blob/8.x-4.x/examples/graphql/example_extension.extension.graphqls)
47+
48+
```
49+
extend type Query {
50+
page(id: Int!): Page
51+
}
52+
```
53+
54+
We are adding a new field inside the built-in `Query` type.
55+
56+
## Add the resolvers
57+
58+
We can now add our resolvers to the Extension class created previously so that our new fields actually resolve something :
59+
60+
```php
61+
<?php
62+
namespace Drupal\graphql_examples\Plugin\GraphQL\SchemaExtension;
63+
use Drupal\graphql\GraphQL\ResolverBuilder;
64+
use Drupal\graphql\GraphQL\ResolverRegistryInterface;
65+
use Drupal\graphql\Plugin\GraphQL\SchemaExtension\SdlSchemaExtensionPluginBase;
66+
/**
67+
* @SchemaExtension(
68+
* id = "example_extension",
69+
* name = "Example extension",
70+
* description = "A simple extension that adds node related fields.",
71+
* schema = "example"
72+
* )
73+
*/
74+
class ExampleSchemaExtension extends SdlSchemaExtensionPluginBase {
75+
/**
76+
* {@inheritdoc}
77+
*/
78+
public function registerResolvers(ResolverRegistryInterface $registry) {
79+
$builder = new ResolverBuilder();
80+
$this->addQueryFields($registry, $builder);
81+
$this->addPageFields($registry, $builder);
82+
}
83+
/**
84+
* @param \Drupal\graphql\GraphQL\ResolverRegistryInterface $registry
85+
* @param \Drupal\graphql\GraphQL\ResolverBuilder $builder
86+
*/
87+
protected function addPageFields(ResolverRegistryInterface $registry, ResolverBuilder $builder) {
88+
$registry->addFieldResolver('Page', 'id',
89+
$builder->produce('entity_id')
90+
->map('entity', $builder->fromParent())
91+
);
92+
$registry->addFieldResolver('Page', 'title',
93+
$builder->compose(
94+
$builder->produce('entity_label')
95+
->map('entity', $builder->fromParent()),
96+
$builder->produce('uppercase')
97+
->map('string', $builder->fromParent())
98+
)
99+
);
100+
}
101+
/**
102+
* @param \Drupal\graphql\GraphQL\ResolverRegistryInterface $registry
103+
* @param \Drupal\graphql\GraphQL\ResolverBuilder $builder
104+
*/
105+
protected function addQueryFields(ResolverRegistryInterface $registry, ResolverBuilder $builder) {
106+
$registry->addFieldResolver('Query', 'page',
107+
$builder->produce('entity_load')
108+
->map('type', $builder->fromValue('node'))
109+
->map('bundles', $builder->fromValue(['page']))
110+
->map('id', $builder->fromArgument('id'))
111+
);
112+
}
113+
}
114+
```
115+
116+
Here we are only resolving the newly added fields. So that the functionality of adding the page to the API is all encapsulated in its own module.

doc/queries/nodes.md

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Now we have an "Article" type in the schema with three fields `id`, `label` and
3636

3737
## Adding resolvers
3838

39-
To add the resolvers we go to our schema implementation and call the appropriate data producers inside the `getResolverRegistry` method.
39+
To add the resolvers we go to our schema implementation and call the appropriate data producers inside the `getResolverRegistry` method. Because our types are extending a common `NodeInterface` we need to also tell what to resolve for a particular type, otherwise it could be an Article or a Page.
4040

4141
```php
4242
/**
@@ -48,32 +48,43 @@ protected function getResolverRegistry() {
4848
'Article' => ContextDefinition::create('entity:node')
4949
->addConstraint('Bundle', 'article'),
5050
]);
51-
51+
52+
// Tell GraphQL how to resolve types of a common interface.
53+
$registry->addTypeResolver('NodeInterface', function ($value) {
54+
if ($value instanceof NodeInterface) {
55+
switch ($value->bundle()) {
56+
case 'article': return 'Article';
57+
case 'page': return 'Page';
58+
}
59+
}
60+
throw new Error('Could not resolve content type.');
61+
});
62+
5263
$registry->addFieldResolver('Query', 'article',
5364
$builder->produce('entity_load')
5465
->map('type', $builder->fromValue('node'))
5566
->map('bundles', $builder->fromValue(['article']))
5667
->map('id', $builder->fromArgument('id'))
5768
]])
5869
);
59-
70+
6071
$registry->addFieldResolver('Article', 'id',
6172
$builder->produce('entity_id')
6273
->map('entity' => $builder->fromParent())
6374
);
64-
75+
6576
$registry->addFieldResolver('Article', 'title',
6677
$builder->produce('entity_label')
6778
->map('entity', $builder->fromParent())
6879
);
69-
80+
7081
$registry->addFieldResolver('Article', 'creator',
7182
$builder->produce('property_path')
7283
->map('type', $builder->fromValue('entity:node'))
7384
->map('value', $builder->fromParent())
7485
->map('path', $builder->fromValue('field_article_creator.value'))
7586
);
76-
87+
7788
return $registry;
7889
}
7990
```

doc/queries/routes.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Querying routes
22

3-
A very powerful feature of Drupal is the ability to manage paths in entities and using path aliases to manage clean URL's. We can leverage data producers provided by the graphql module to query these paths from Drupal.
3+
A very powerful feature of Drupal is the ability to manage paths in entities and using path aliases to manage clean URL's. We can leverage data producers provided by the GraphQL module to query these paths from Drupal.
44

55
In this section we will look at how we can load a node from it's URL.
66

@@ -29,7 +29,7 @@ interface NodeInterface {
2929
3030
```
3131

32-
In this schema we can see that we can query a node by its route, that takes a parameter called "path". We also (similar to the test schema that comes with the module) have a "NodeInterface" and an "Article" type that implements that interface. That means our "Article" will be available inside a fragment in the NodeInterface type.
32+
In this schema we can see that we can query a node by its route, that takes a parameter called "path". We also (similar to the test schema that comes with the module) have a `NodeInterface` and an "Article" type that implements that interface. That means our "Article" will be available inside a fragment in the `NodeInterface` type.
3333

3434
## Adding resolvers
3535

@@ -42,6 +42,17 @@ To add the resolvers we go to our schema implementation and call the appropriate
4242
protected function getResolverRegistry() {
4343
...
4444

45+
// Tell GraphQL how to resolve types of a common interface.
46+
$registry->addTypeResolver('NodeInterface', function ($value) {
47+
if ($value instanceof NodeInterface) {
48+
switch ($value->bundle()) {
49+
case 'article': return 'Article';
50+
case 'page': return 'Page';
51+
}
52+
}
53+
throw new Error('Could not resolve content type.');
54+
});
55+
4556
$registry->addFieldResolver('Query', 'route', $builder->compose(
4657
$builder->produce('route_load')
4758
->map('path', $builder->fromArgument('path')),

doc/starting/custom-schema.md

Lines changed: 9 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2,80 +2,20 @@
22

33
The best way to start making a new schema is to take the example schema provided by the graphql_examples module in `/graphql/examples/` and copy all the files from the example module to a custom module of your own. By doing this you can then start adapting the schema to your needs including your own content types and making them available in the schema.
44

5-
## Clone the SdlSchemaTest (deprecated)
6-
7-
First create your own module and call it `mydrupalgql`. Make sure you have a .info file inside the module to make sure drupal will know about this module (for more info see [Custom modules in drupal](https://www.drupal.org/docs/8/creating-custom-modules)).
8-
Then head to the graphql module folder and copy the content of `src/Plugin/GraphQL/Schema/SdlSchemaTest.php`.
9-
Inside `modules/mydrupalgql` create a file for your custom schema using this structure `src/Plugin/GraphQL/Schema/SdlSchemaMyDrupalGql.php` and paste the content of the previous file. Make sure to adapt the namespaces on the top of the file. In the end it should look something like this (some parts of the schema are marked with `...` for simplicity):
10-
11-
```php
12-
13-
namespace Drupal\mydrupalgql\Plugin\GraphQL\Schema;
14-
15-
use Drupal\Core\Plugin\Context\ContextDefinition;
16-
use Drupal\graphql\GraphQL\ResolverBuilder;
17-
use Drupal\graphql\GraphQL\ResolverRegistry;
18-
use Drupal\graphql\Plugin\GraphQL\Schema\SdlSchemaPluginBase;
19-
20-
/**
21-
* @Schema(
22-
* id = "mydrupalgql",
23-
* name = "My Drupal Graphql Schema"
24-
* )
25-
* @codeCoverageIgnore
26-
*/
27-
class SdlSchemaMyDrupalGql extends SdlSchemaPluginBase {
28-
29-
/**
30-
* {@inheritdoc}
31-
*/
32-
protected function getSchemaDefinition() {
33-
return <<<GQL
34-
schema {
35-
query: Query
36-
}
5+
## Enable the custom schema
376

38-
type Query {
39-
article(id: Int!): Article
40-
page(id: Int!): Page
41-
node(id: Int!): NodeInterface
42-
label(id: Int!): String
43-
}
7+
Go back to the list of servers under `/admin/config/graphql` to create a new server for your custom schema. When creating the server choose the "My Drupal Graphql Schema" as the schema. After saving click "Explorer". This will take you to the "GraphiQL" page, where you can explore your custom schema.
448

45-
type Article implements NodeInterface {
46-
id: Int!
47-
uid: String
48-
title: String!
49-
render: String
50-
}
9+
## Adapt module to your needs
5110

52-
...
11+
Inside `/graphql` you will find some files that are a common `.graphqls` file that your editor will likely pick up as GraphQL files already. They include the schema definition for your custom schema.
5312

54-
interface NodeInterface {
55-
id: Int!
56-
}
57-
GQL;
58-
}
13+
### example.graphqls
5914

60-
/**
61-
* {@inheritdoc}
62-
*/
63-
protected function getResolverRegistry() {
64-
$builder = new ResolverBuilder();
65-
$registry = new ResolverRegistry([
66-
'Article' => ContextDefinition::create('entity:node')
67-
->addConstraint('Bundle', 'article'),
68-
'Page' => ContextDefinition::create('entity:node')
69-
->addConstraint('Bundle', 'page'),
70-
]);
15+
This is the main entry point for your schema. You can insert new types and fields into types here as needed for your use case. Simply adding new types and fields here will not affect the API right away, they will be available but will not yet resolve anything, as we didn't add any resolvers yet.
7116

72-
...
17+
If your requirements are simple this file should be enough to get started.
7318

74-
return $registry;
75-
}
76-
}
77-
```
19+
### Extensions
7820

79-
## Enable the custom schema
80-
81-
Go back to the list of servers under `/admin/config/graphql` to create a new server for your custom schema. When creating the server choose the "My Drupal Graphql Schema" as the schema. After saving click "Explorer". This will take you to the "GraphiQL" page, where you can explore your custom schema.
21+
`example.extension.base.graphqls` and `example_extension.extension.graphqls` are files that can be added on top of the existing schema file to "extend" the schema with new functionality. We will approach this in the Advanced section when talking about spliting schemas so that you can make certain modules enable new functionalities as they are enabled.

0 commit comments

Comments
 (0)