Extending the Schema

Adding a custom model
Add a new class-backed type beyond DataObject
Adding middleware
Add middleware to to extend query execution
Adding a custom operation
Add a new operation for model types
The global schema
How to push modifications to every schema in the project
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

Adding a custom model

The only point of contact the silverstripe-graphql schema has with Silverstripe specifically is through the DataObjectModel adapter and its associated plugins. This is important, because it means you can plug in any schema-aware class as a model, and it will be afforded all the same features as DataObjects.

It is, however, hard to imagine a model-driven type that isn't related to an ORM, so we'll keep this section simple and just describe what the requirements are rather than think up an outlandish example of what a non-DataObject model might be.

SchemaModelInterface

Models must implement the SchemaModelInterface, which has some hefty requirements. Let's walk through them:

  • getIdentifier(): string: A unique identifier for this model type, e.g. 'DataObject'
  • hasField(string $fieldName): bool: Return true if $fieldName exists on the model
  • getTypeForField(string $fieldName): ?string: Given a field name, infer the type. If the field doesn't exist, return null
  • getTypeName(): string: Get the name of the type (i.e. based on the source class)
  • getDefaultResolver(?array $context = []): ResolverReference: Get the generic resolver that should be used for types that are built with this model.
  • getSourceClass(): string: Get the name of the class that builds the type, e.g. MyDataObject
  • getAllFields(): array: Get all availble fields on the object
  • getModelField(string $fieldName): ?ModelType: For nested fields. If a field resolves to another model (e.g. has_one), return that model type.

In addition, models may want to implement:

  • OperationProvider (if your model creates operations, like read, create, etc)
  • DefaultFieldsProvider (if your model provides a default list of fields, e.g. id)
  • DefaultPluginProvider (if your model can supply a default set of plugins, e.g. default_plugins on DataObjectModel)

This is all a lot to take in out of context. A good exercise would be to look through how DataObjectModel implements all these methods.

SchemaModelCreatorInterface

Given a class name, create an instance of SchemaModelInterface. This layer of abstraction is necessary because we can't assume that all SchemaModelInterfaces will accept a class name in their constructors.

Implementors of this interface just need to be able to report whether they apply to a given class and create a model given a class name.

Look at the DataObjectModel implementation:

class ModelCreator implements SchemaModelCreatorInterface
{
    use Injectable;

    /**
     * @param string $class
     * @return bool
     */
    public function appliesTo(string $class): bool
    {
        return is_subclass_of($class, DataObject::class);
    }

    /**
     * @param string $class
     * @return SchemaModelInterface
     */
    public function createModel(string $class): SchemaModelInterface
    {
        return DataObjectModel::create($class);
    }
}

Registering your model creator

Just add it to the registry:

**app/_graphql/config.yml

modelCreators:
  - 'SilverStripe\GraphQL\Schema\DataObject\ModelCreator'

Further reading

Adding a custom model
Add a new class-backed type beyond DataObject
Adding middleware
Add middleware to to extend query execution
Adding a custom operation
Add a new operation for model types
The global schema
How to push modifications to every schema in the project