silverstripe-framework/docs/en/02_Developer_Guides/19_GraphQL/05_plugins/01_overview.md

5.6 KiB

title summary
What are plugins? An overview of how plugins work with the GraphQL schema

Plugins

[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, and report any issues at github.com/silverstripe/silverstripe-graphql. Docs for the current stable version (3.x) can be found here [/alert]

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 DataObject queries
  • Adding a one-off VerisionedStage enum to the schema
  • Ensuring Member is in the schema
  • And many more...

Default plugins

By default, all schemas ship with some plugins installed that will benefit most use cases:

  • The DataObject model (i.e. any dataobject based type) has:
    • An inheritance plugin that builds the __extends field, and merges ancestral fields.
    • An inheritedPlugins plugin (a bit meta!) that merges plugins from ancestral types into descendants. installed).
  • The read and readOne operations have:
    • A canView plugin for hiding records that do not pass a canView() check
  • The read operation has:
    • A paginateList plugin for adding pagination arguments and types (e.g. nodes)

In addition to the above, the default schema specifically ships with an even richer set of default plugins, including:

  • A versioning plugin that adds version fields to the dataobject type (if silverstripe/versioned is installed)
  • A readVersion plugin (if silverstripe/versioned is installed) that allows versioned operations on read and readOne queries.
  • A filter plugin for filtering queries (adds a filter argument)
  • A sort plugin for sorting queries (adds a sort argument)

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 default schema.

Overriding default plugins

You can override default plugins generically in the modelConfig section.

app/_graphql/modelConfig.yml

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
MyProject\MyCustomPage: {} # now has no inheritance plugin 

Likewise, you can do the same for operations:

app/_graphql/models.yml

Page:
 operations:
   read:
     plugins:
       readVersion: false
MyProject\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 namespace SilverStripe\GraphQL\Schema\Interfaces

  • SchemaUpdater: Make a one-off, context-free update to the schema
  • QueryPlugin: Update a generic query
  • MutationPlugin: Update a generic mutation
  • TypePlugin: Update a generic type
  • FieldPlugin: Update a field on a generic type
  • ModelQueryPlugin: Update queries generated by a model, e.g. readPages
  • ModelMutationPlugin: Update mutations generated by a model, e.g. createPage
  • ModelTypePlugin: Update types that are generated by a model
  • ModelFieldPlugin: 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 with Injector.

SilverStripe\Core\Injector\Injector:
  SilverStripe\GraphQL\Schema\Registry\PluginRegistry:
    constructor:
      myPlugin: '%$MyProject\Plugins\MyPlugin'

[info] The key myPlugin is arbitrary. The identifier of the plugin is obtained procedurally. [/info]

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.

Further reading

[CHILDREN]