Plugins#
NOTE
You are viewing docs for silverstripe/graphql 4.x. If you are using 3.x, documentation can be found in the GitHub repository
What are plugins?#
Plugins are used to distribute reusable functionality across your schema. Some examples of commonly used plugins include:
- Adding versioning arguments to versioned DataObjects
- Adding a custom filter/sort arguments to
DataObjectqueries - Adding a one-off
VerisionedStageenum to the schema - Ensuring
Memberis in the schema - And many more...
Default plugins#
By default, all schemas ship with some plugins installed that will benefit most use cases:
- The
DataObjectmodel (i.e. anyDataObjectbased type) has:- An
inheritanceplugin that builds the interfaces, unions, and merges ancestral fields. - An
inheritedPluginsplugin (a bit meta!) that merges plugins from ancestral types into descendants. installed).
- An
- The
readandreadOneoperations have:- A
canViewplugin for hiding records that do not pass acanView()check
- A
- The
readoperation has:- A
paginateListplugin for adding pagination arguments and types (e.g.nodes)
- A
In addition to the above, the default schema specifically ships with an even richer set of default
plugins, including:
- A
versioningplugin that addsversionfields to theDataObjecttype (ifsilverstripe/versionedis installed) - A
readVersionplugin (ifsilverstripe/versionedis installed) that allows versioned operations onreadandreadOnequeries. - A
filterplugin for filtering queries (adds afilterargument) - A
sortplugin for sorting queries (adds asortargument)
All of these are defined in the modelConfig section of the schema (see configuring your schema).
For reference, see the GraphQL configuration in silverstripe/admin, which applies
these default plugins to the admin schema.
Overriding default plugins#
You can override default plugins generically in the modelConfig section.
# app/_graphql/config.yml
modelConfig:
DataObject:
plugins:
inheritance: false # No `DataObject` models get this plugin unless opted into
operations:
read:
plugins:
paginateList: false # No `DataObject` models have paginated read operations unless opted into
You can override default plugins on your specific DataObject type and these changes will be inherited by descendants.
# app/_graphql/models.yml
Page:
plugins:
inheritance: false
App\PageType\MyCustomPage: {} # now has no inheritance plugin
Likewise, you can do the same for operations:
# app/_graphql/models.yml
Page:
operations:
read:
plugins:
readVersion: false
App\PageType\MyCustomPage:
operations:
read: true # has no readVersion plugin
What plugins must do#
There isn't a huge API surface to a plugin. They just have to:
- Implement at least one of several plugin interfaces
- Declare an identifier
- Apply themselves to the schema with the
apply(Schema $schema)method - Be registered with the
PluginRegistry
Available plugin interfaces#
Plugin interfaces are all found in the SilverStripe\GraphQL\Schema\Interfaces namespace
SchemaUpdater: Make a one-off, context-free update to the schemaQueryPlugin: Update a generic queryMutationPlugin: Update a generic mutationTypePlugin: Update a generic typeFieldPlugin: Update a field on a generic typeModelQueryPlugin: Update queries generated by a model, e.g.readPagesModelMutationPlugin: Update mutations generated by a model, e.g.createPageModelTypePlugin: Update types that are generated by a modelModelFieldPlugin: Update a field on types generated by a model
Wow, that's a lot of interfaces, right? This is owing mostly to issues around strict typing between interfaces, and allows for a more expressive developer experience. Almost all of these interfaces have the same requirements, just for different types. It's pretty easy to navigate if you know what you want to accomplish.
Registering plugins#
Plugins have to be registered to the PluginRegistry via the Injector.
SilverStripe\Core\Injector\Injector:
SilverStripe\GraphQL\Schema\Registry\PluginRegistry:
constructor:
- 'App\GraphQL\Plugin\MyPlugin'
Resolver middleware and afterware#
The real power of plugins is the ability to distribute not just configuration across the schema, but more importantly, functionality.
Fields have their own resolvers already, so we can't really get into those to change their functionality without a massive hack. This is where the idea of resolver middleware and resolver afterware comes in really useful.
Resolver middleware runs before the field's assigned resolver Resolver afterware runs after the field's assigned resolver
Middlewares and afterwares are pretty straightforward. They get the same $args, $context, and $info
parameters as the assigned resolver, but the first argument, $result is mutated with each resolver.