DOCS: improve graphql inheritance docs

This commit is contained in:
Aaron Carlino 2021-06-30 19:38:11 +12:00
parent 0537e769fc
commit 29320841c9
2 changed files with 57 additions and 70 deletions

47
.idea/workspace.xml generated
View File

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="49c2db15-d223-4efe-b1e8-2d4ecfdaa74a" name="Default Changelist" comment="">
<change beforePath="$PROJECT_DIR$/docs/en/00_Getting_Started/index.md" beforeDir="false" afterPath="$PROJECT_DIR$/docs/en/00_Getting_Started/index.md" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ComposerSettings" synchronizationState="SYNCHRONIZE">
<pharConfigPath>$PROJECT_DIR$/composer.json</pharConfigPath>
<execution />
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProjectId" id="1ueqw0dJJFnqPFjpLOJN1mIgTXB" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" />
<property name="nodejs_npm_path_reset_for_default_project" value="true" />
<property name="vue.rearranger.settings.migration" value="true" />
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="49c2db15-d223-4efe-b1e8-2d4ecfdaa74a" name="Default Changelist" comment="" />
<created>1625036637006</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1625036637006</updated>
<workItem from="1625036638810" duration="456000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
</project>

View File

@ -73,8 +73,8 @@ But what about when we want more than `title` and `content`? In some cases, we'l
query { query {
readPages { readPages {
nodes { nodes {
title title # Common field
content content # Common field
... on HomePage { ... on HomePage {
heroImage { heroImage {
url url
@ -98,22 +98,23 @@ that only appear on some types, we need to be explicit.
But let's take this a step further. What if there's another class in between? Imagine this ancestry: But let's take this a step further. What if there's another class in between? Imagine this ancestry:
``` ```
Page: Page
EventPage: -> EventPage
ConferencePage -> ConferencePage
WebinarPage -> WebinarPage
``` ```
We can use the intermediary interface `EventPageInterface` to consolidate fields that are unique to We can use the intermediary interface `EventPageInterface` to consolidate fields that are unique to
`ConferencePage` and `WebinarPage`. `ConferencePage` and `WebinarPage`.
``` ```graphql
query { query {
readPages { readPages {
nodes { nodes {
title title # Common to all types
content content # Common to all types
... on EventPageInterface { ... on EventPageInterface {
# Common fields for WebinarPage, ConferencePage, EventPage
numberOfTickets numberOfTickets
featuredSpeaker { featuredSpeaker {
firstName firstName
@ -147,10 +148,12 @@ which is usually the parent class with the "Interface" suffix.
### Inheritance: A deep dive ### Inheritance: A deep dive
There are two components to the way inheritance is handled at build time: There are several ways inheritance is handled at build time:
* Implicit field / type exposure * Implicit field / type exposure
* Interface generation and assignment to types * Interface generation
* Assignment of generated interfaces to types
* Assignment of generated interfaces to queries
We'll look at each of these in detail. We'll look at each of these in detail.
@ -180,7 +183,7 @@ GalleryPage:
This results in these two types being exposed with the fields as shown, but also results in a `Page` type: This results in these two types being exposed with the fields as shown, but also results in a `Page` type:
``` ```graphql
type Page { type Page {
id: ID! # always exposed id: ID! # always exposed
title: String title: String
@ -189,7 +192,7 @@ type Page {
} }
``` ```
#### Interface generation and assignment to types #### Interface generation
Any type that's part of an inheritance chain will generate interfaces. Each applicable ancestral interface is added Any type that's part of an inheritance chain will generate interfaces. Each applicable ancestral interface is added
to the type. Like the type inheritance pattern shown above, interfaces duplicate fields from their ancestors as well. to the type. Like the type inheritance pattern shown above, interfaces duplicate fields from their ancestors as well.
@ -202,16 +205,16 @@ All of this is serviced by: `SilverStripe\GraphQL\Schema\DataObject\InterfaceBui
##### Example ##### Example
``` ```
Page: Page
BlogPage extends Page -> BlogPage extends Page
EventsPage extends Page -> EventsPage extends Page
ConferencePage extends EventsPage -> ConferencePage extends EventsPage
WebinarPage extends EventsPage -> WebinarPage extends EventsPage
``` ```
This will create the following interfaces: This will create the following interfaces:
``` ```graphql
interface PageInterface { interface PageInterface {
title: String title: String
content: String content: String
@ -249,9 +252,11 @@ interface WebinarPageInterface {
} }
``` ```
Types then get these interfaces applied, like so: #### Interface assignment to types
``` The generated interfaces then get applied to the appropriate types, like so:
```graphql
type Page implements PageInterface {} type Page implements PageInterface {}
type BlogPage implements BlogPageInterface & PageInterface {} type BlogPage implements BlogPageInterface & PageInterface {}
type EventsPage implements EventsPageInterface & PageInterface {} type EventsPage implements EventsPageInterface & PageInterface {}
@ -261,17 +266,46 @@ type WebinarPage implements WebinarPageInterface & EventsPageInterface & PageInt
Lastly, for good measure, we create a `DataObjectInterface` that applies to everything. Lastly, for good measure, we create a `DataObjectInterface` that applies to everything.
``` ```graphql
interface DataObjectInterface { interface DataObjectInterface {
id: ID! id: ID!
# Any other fields you've explicitly exposed in config.modelConfig.DataObject.base_fields # Any other fields you've explicitly exposed in config.modelConfig.DataObject.base_fields
} }
``` ```
``` ```graphql
type Page implements PageInterface & DataObjectInterface {} type Page implements PageInterface & DataObjectInterface {}
``` ```
#### Interface assignment to queries
Queries, both at the root, and nested as fields on types, will have their types
updated if they refer to a type that has had any generated interfaces added to it.
```graphql
type Query {
readPages: [Page]
}
type BlogPage {
download: File
}
```
Becomes:
```graphql
type Query {
readPages: [PageInterface]
}
type BlogPage {
download: FileInterface
}
```
All of this is serviced by: `SilverStripe\GraphQL\Schema\DataObject\InterfaceBuilder`
#### Elemental #### Elemental
Almost by definition, content blocks are always abstractions. You're never going to query for a `BaseElement` type Almost by definition, content blocks are always abstractions. You're never going to query for a `BaseElement` type