From ad91379a5d0c1a2e9598da051b98fbab999d53f7 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Thu, 2 Jun 2022 13:35:41 +1200 Subject: [PATCH] More updates to doc --- .../00_Server_Requirements.md | 3 +- .../01_activating_the_server.md | 30 ++-- .../02_configuring_your_schema.md | 133 ++++++++------- .../03_building_the_schema.md | 73 +++++---- ...al_code.md => 04_using_procedural_code.md} | 45 +++--- .../05_deploying_the_schema.md | 111 +++++-------- .../19_GraphQL/01_getting_started/index.md | 14 +- .../01_adding_dataobjects_to_the_schema.md | 153 ++++++++++-------- .../02_query_plugins.md | 18 +-- .../03_permissions.md | 8 +- .../04_inheritance.md | 19 +-- .../05_versioning.md | 10 +- .../06_property_mapping.md | 3 +- .../02_working_with_dataobjects/index.md | 10 +- .../01_creating_a_generic_type.md | 2 +- .../02_building_a_custom_query.md | 24 +-- .../03_resolver_discovery.md | 2 +- .../03_working_with_generic_types/index.md | 11 +- .../04_security_and_best_practices/index.md | 4 +- .../19_GraphQL/05_plugins/01_overview.md | 40 ++--- .../05_plugins/03_writing_a_complex_plugin.md | 13 +- .../19_GraphQL/05_plugins/index.md | 4 +- .../06_extending/adding_a_custom_operation.md | 2 +- .../06_extending/adding_middleware.md | 2 +- .../19_GraphQL/06_extending/index.md | 4 +- .../19_GraphQL/07_tips_and_tricks.md | 5 +- .../19_GraphQL/08_architecture_diagrams.md | 23 +-- .../02_Developer_Guides/19_GraphQL/index.md | 6 +- docs/en/04_Changelogs/4.11.0.md | 17 +- 29 files changed, 399 insertions(+), 390 deletions(-) rename docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/{06_using_procedual_code.md => 04_using_procedural_code.md} (69%) diff --git a/docs/en/00_Getting_Started/00_Server_Requirements.md b/docs/en/00_Getting_Started/00_Server_Requirements.md index 455cc72e0cd..cbaf329b53f 100644 --- a/docs/en/00_Getting_Started/00_Server_Requirements.md +++ b/docs/en/00_Getting_Started/00_Server_Requirements.md @@ -171,7 +171,8 @@ noisy, here's some pointers for auto-generated files to trigger and include in a * `.graphql-generated/` and `public/_graphql/`: Schema and type definitions required by CMS and any GraphQL API endpoint. Generated by [silverstripe/graphql v4](https://github.com/silverstripe/silverstripe-graphql). See - [GraphQL Schema Build](/developer_guides/graphql/getting_started/building_the_schema). + [building the schema](/developer_guides/graphql/getting_started/building_the_schema) and + [deploying the schema](/developer_guides/graphql/getting_started/deploying_the_schema). * Various recipes create default files in `app/` and `public/` on `composer install` and `composer update` via [silverstripe/recipe-plugin](https://github.com/silverstripe/recipe-plugin). diff --git a/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/01_activating_the_server.md b/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/01_activating_the_server.md index 8e3173647a9..ad5cf262e31 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/01_activating_the_server.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/01_activating_the_server.md @@ -21,15 +21,21 @@ Docs for the current stable version (3.x) can be found GraphQL is used through a single route, typically `/graphql`. You need to define *types* and *queries* to expose your data via this endpoint. While this recommended route is left open for you to configure on your own, the modules contained in the [CMS recipe](https://github.com/silverstripe/recipe-cms), - (e.g. `asset-admin`) run off a separate GraphQL server with its own endpoint - (`admin/graphql`) with its own permissions and schema. +(e.g. `silverstripe/asset-admin`) run off a separate GraphQL server with its own endpoint +(`admin/graphql`) with its own permissions and schema. These separate endpoints have their own identifiers. `default` refers to the GraphQL server -in the user space (e.g. `/graphql`) while `admin` refers to the GraphQL server used by CMS modules -(`admin/graphql`). You can also [set up a new schema](#setting-up-a-custom-graphql-server) if you wish. +in the user space (e.g. `/graphql`) - i.e. your custom schema, while `admin` refers to the +GraphQL server used by CMS modules (`admin/graphql`). You can also [set up a new schema server](#setting-up-a-custom-graphql-server) +if you wish. -By default, this module does not route any GraphQL servers. To activate the default, -public-facing GraphQL server that ships with the module, just add a rule to `Director`. +[info] +The word "server" here refers to a route with its own isolated GraphQL schema. It does +not refer to a web server. +[/info] + +By default, `silverstripe/graphql` does not route any GraphQL servers. To activate the default, +public-facing GraphQL server that ships with the module, just add a rule to [`Director`](api:SilverStripe\Control\Director). ```yaml SilverStripe\Control\Director: @@ -52,10 +58,8 @@ SilverStripe\Core\Injector\Injector: class: SilverStripe\GraphQL\Controller constructor: schemaKey: myNewSchema - ``` - We'll now need to route the controller. ```yaml @@ -64,14 +68,8 @@ SilverStripe\Control\Director: 'my-graphql': '%$SilverStripe\GraphQL\Controller.myNewSchema' ``` -Now, you're ready to [configure your schema](configuring_your_schema.md). - -```yaml -SilverStripe\GraphQL\Schema\Schema: - schemas: - myNewSchema: - # ... -``` +Now, once you have [configured](configuring_your_schema) and [built](building_the_schema) your schema, you +can access it at `/my-graphql`. ### Further reading diff --git a/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/02_configuring_your_schema.md b/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/02_configuring_your_schema.md index f7e9828ee88..f67ea7ed0b2 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/02_configuring_your_schema.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/02_configuring_your_schema.md @@ -1,6 +1,7 @@ --- title: Configuring your schema summary: Add a basic type to the schema configuration +icon: code --- # Getting started @@ -22,10 +23,10 @@ GraphQL is a strongly-typed API layer, so having a schema behind it is essential * A schema consists of **[types](https://graphql.org/learn/schema/#type-system)** * **Types** consist of **[fields](https://graphql.org/learn/queries/#fields)** * **Fields** can have **[arguments](https://graphql.org/learn/queries/#arguments)**. -* **Fields** need to **[resolve](https://graphql.org/learn/execution/#root-fields-resolvers)** +* **Fields** need to be **[resolved](https://graphql.org/learn/execution/#root-fields-resolvers)** **Queries** are just **fields** on a type called "query". They can take arguments, and they -must resolve. +must be resolved. There's a bit more to it than that, and if you want to learn more about GraphQL, you can read the [full documentation](https://graphql.org/learn/), but for now, these three concepts will @@ -42,7 +43,7 @@ SilverStripe\GraphQL\Schema\Schema: # your schemas here ``` -Let's populate a schema that is pre-configured for us out of the box, `default`. +Let's populate a schema that is pre-configured for us out of the box called "default". **app/_config/graphql.yml** ```yml @@ -54,7 +55,7 @@ SilverStripe\GraphQL\Schema\Schema: types: # your generic types here models: - # your dataobjects here + # your DataObjects here queries: # your queries here mutations: @@ -63,15 +64,21 @@ SilverStripe\GraphQL\Schema\Schema: ### Avoid config flushes -Because the schema YAML is only consumed at build time and never used at runtime, it doesn't +Because the schema definition is only consumed at build time and never used at runtime, it doesn't make much sense to store it in the configuration layer, because it just means you'll have to `flush=1` every time you make a schema update, which will slow down your builds. It is recommended that you store your schema YAML **outside of the _config directory** to -increase performance and remove the need for flushing. +increase performance and remove the need for flushing when you [build your schema](building_the_schema). -We can do this by adding a `src` key to our schema definition that maps to a directory -relative to the project root. +[notice] +This doesn't mean there is never a need to `flush=1` when building your schema. If you were to add a new +schema, or make a change to the value of this `src` attribute, those are still a standard config +changes which won't take effect without a flush. +[/notice] + +We can do this by adding a `src` key to our `app/_config/graphql.yml` schema definition +that maps to a directory relative to the project root. **app/_config/graphql.yml** ```yml @@ -82,13 +89,11 @@ SilverStripe\GraphQL\Schema\Schema: - app/_graphql ``` -Your `src` must be an array. This allows further source files to be merged into your schema. +Your `src` must be an array. This allows further source files to be merged into your schema. This feature can be use to extend the schema of third party modules. [info] -Your directory can also be a module reference, e.g. `somevendor/somemodule: _graphql` -[/info] - +Your directory can also be relative to a module reference, e.g. `somevendor/somemodule: _graphql`: **app/_config/graphql.yml** ```yml @@ -101,17 +106,9 @@ SilverStripe\GraphQL\Schema\Schema: # The next line would map to `vendor/somevendor/somemodule/_graphql` - 'somevendor/somemodule: _graphql' ``` +[/info] - - -Now, in our `app/_graphql` file, we can create YAML file definitions. - -[notice] -This doesn't mean there is never a need to flush your schema config. If you were to add a new - one, or make a change to the value of this `src` attribute, those are still a standard config changes. -[/notice] - - +Now, in the new `app/_graphql` folder, we can create YAML file definitions. **app/_graphql/schema.yml** ```yaml @@ -121,7 +118,7 @@ config: types: # your generic types here models: - # your dataobjects here + # your DataObjects here bulkLoad: # your bulk loader directives here queries: @@ -137,77 +134,94 @@ like this. We can tidy this up quite a bit by simply placing the files in direct to the keys they populate -- e.g. `config/`, `types/`, `models/`, `queries/`, `mutations/`, etc. There are two approaches to namespacing: + * By filename * By directory name -##### Namespacing by directory name +##### Namespacing by filename -If you use a parent directory name (at any depth) of one of the four keywords above, it will -be implicitly placed in the corresponding section of the schema. +If the filename is named one of the four keywords above, it will be implicitly placed +in the corresponding section of the schema - e.g. any configuration +added to `app/_graphql/config.yml` will be implicitly added to +`SilverStripe\GraphQL\Schema\Schema.schemas.default.config`. + +**This only works in the root source directory** (i.e. `app/_graphql/some-directory/config.yml` +will not work). -**app/_graphql/types/config.yml** +**app/_graphql/config.yml** ```yaml -# my schema config here +# my config here ``` -**app/_graphql/types/types.yml** +**app/_graphql/types.yml** ```yaml -# my type definitions here +# my types here ``` -**app/_graphql/models/models.yml** +**app/_graphql/models.yml** ```yaml -# my type definitions here +# my models here ``` -**app/_graphql/bulkLoad/bulkLoad.yml** +**app/_graphql/bulkLoad.yml** ```yaml # my bulk loader directives here ``` -##### Namespacing by filename +##### Namespacing by directory name -If the filename is named one of the four keywords above, it will be implicitly placed -in the corresponding section of the schema. **This only works in the root source directory**. +If you use a parent directory name (at any depth) of one of the four keywords above, it will +be implicitly placed in the corresponding section of the schema - e.g. any configuration +added to a `.yml` file in `app/_graphql/config/` will be implicitly added to +`SilverStripe\GraphQL\Schema\Schema.schemas.default.config`. -**app/_graphql/types.yml** +[hint] +The names of the actual files here do not matter. You could for example have a separate file +for each of your types, e.g. `app/_graphql/types/my-first-type.yml`. +[/hint] + +**app/_graphql/config/config.yml** +```yaml +# my config here +``` + +**app/_graphql/types/types.yml** ```yaml # my types here ``` -**app/_graphql/models.yml** +**app/_graphql/models/models.yml** ```yaml # my models here ``` -**app/_graphql/bulkLoad.yml** +**app/_graphql/bulkLoad/bulkLoad.yml** ```yaml # my bulk loader directives here ``` -#### Going even more granular +##### Going even more granular These special directories can contain multiple files that will all merge together, so you can even create one file per type, or some other convention. All that matters is that the parent directory name -matches one of the schema keys. +_or_ the filename matches one of the schema keys. The following are perfectly valid: -* `app/_graphql/types/mySingleType.yml` -* `app/_graphql/models/allElementalBlocks.yml` -* `app/_graphql/news-and-blog/models/blog.yml` -* `app/_graphql/mySchema.yml` +* `app/_graphql/config/config.yml` maps to `SilverStripe\GraphQL\Schema\Schema.schemas.default.config` +* `app/_graphql/types/allElementalBlocks.yml` maps to `SilverStripe\GraphQL\Schema\Schema.schemas.default.types` +* `app/_graphql/news-and-blog/models/blog.yml` maps to `SilverStripe\GraphQL\Schema\Schema.schemas.default.models` +* `app/_graphql/mySchema.yml` maps to `SilverStripe\GraphQL\Schema\Schema.schemas.default` ### Schema config -In addition to all the keys mentioned above, each schema can declare a generic - configuration section, `config`. This are mostly used for assigning or removing plugins - and resolvers. +Each schema can declare a generic configuration section, `config`. This is mostly used for assigning +or removing plugins and resolvers. An important subsection of `config` is `modelConfig`, where you can configure settings for specific models, e.g. `DataObject`. -Like the other sections, it can have its own `config.yml`, or just be added as a `config:` +Like the other sections, it can have its own `config.yml`, or just be added as a `config:` mapping to a generic schema yaml document. **app/_graphql/config.yml** @@ -223,11 +237,19 @@ modelConfig: paginateList: false ``` +You can learn more about plugins and resolvers in the [qeury plugins](../working_with_dataobjects/query_plugins), +[plugins](../plugins), [building a custom query](../working_with_generic_types/building_a_custom_query#building-a-custom-query), +and [resolver discovery](../working_with_generic_types/resolver_discovery) sections. ### Defining a basic type Let's define a generic type for our GraphQL schema. +[info] +Generic types don't map to `DataObject` classes - they're useful for querying more 'generic' data (hence the name). +You'll learn more about adding DataObjects in [working with DataObjects](../working_with_DataObjects). +[/info] + **app/_graphql/types.yml*** ```yaml Country: @@ -238,8 +260,11 @@ Country: languages: '[String]' ``` -If you're familiar with [GraphQL type language](https://graphql.org/learn/schema/#type-language), this should look pretty familiar. There are only a handful of [scalar types](https://graphql.org/learn/schema/#scalar-types) available in -GraphQL by default. They are: +If you're familiar with [GraphQL type language](https://graphql.org/learn/schema/#type-language), +this should look pretty familiar. + +There are only a handful of [scalar types](https://graphql.org/learn/schema/#scalar-types) +available in GraphQL by default. They are: * String * Int @@ -253,13 +278,13 @@ To define a type as required (non-null), you add an exclamation mark: `String!` Often times, you may want to do both: `[String!]!` [notice] -Look out for the footgun, here. Make sure your bracketed type is in quotes, otherwise it's valid YAML that will get parsed as an array! +Look out for the footgun, here. Make sure your bracketed type is in quotes +(i.e. `'[String]'`, not `[String]`), otherwise it's valid YAML that will get parsed as an array! [/notice] That's all there is to it! To learn how we can take this further, check out the [working with generic types](../working_with_generic_types) documentation. Otherwise, -let's get started on [**adding some dataobjects**](../working_with_dataobjects). - +let's get started on [**adding some DataObjects**](../working_with_DataObjects). ### Further reading diff --git a/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/03_building_the_schema.md b/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/03_building_the_schema.md index 78674cba9b6..e59d7597d27 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/03_building_the_schema.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/03_building_the_schema.md @@ -1,6 +1,7 @@ --- title: Building the schema summary: Turn your schema configuration into executable code +icon: hammer --- # Getting started @@ -18,44 +19,55 @@ Docs for the current stable version (3.x) can be found ## Building the schema The primary API surface of the `silverstripe/graphql` module is the yaml configuration, along -with some [procedural configuration](using_procedual_code). It is important to understand +with some [procedural configuration](using_procedural_code). It is important to understand that **none of this configuration gets interpreted at runtime**. Loading the schema configuration (which we refer to as the "schema definition") at runtime and converting it to executable code has dire effects on performance, making API requests slower and slower as the schema grows larger. To mitigate this problem, the schema that gets executed at runtime is **generated PHP code**. This code generation happens during a build step, and it is critical to run this build step -whenever the schema changes. +whenever the schema definition changes, or a new schema definition is added. -### Running the build +### What triggers a GraphQL code build? -The task that generates the schema code is `dev/graphql/build`. +- Any time you run the `dev/graphql/build` command to explicitly build your GraphQL schemas. +- Any time you run the `dev/build` command on your project. +- `silverstripe/graphql` will attempt to generate your schema "on-demand" on the first GraphQL request _only_ if it wasn’t already generated. + +[warning] +Relying on the "on-demand" schema generation on the first GraphQL request requires some additional consideration. +See [deploying the schema](deploying_the_schema#on-demand). +[/warning] + +#### Running `dev/graphql/build` + +The main command for generating the schema code is `dev/graphql/build`. `vendor/bin/sake dev/graphql/build` -This task takes an optional `schema` parameter. If you only want to generate a specific schema +This command takes an optional `schema` parameter. If you only want to generate a specific schema (e.g. generate your custom schema, but not the CMS schema), you should pass in the name of the schema you want to build. +[info] +If you do not provide a `schema` parameter, the command will build all schemas. +[/info] + `vendor/bin/sake dev/graphql/build schema=default` [info] Most of the time, the name of your custom schema is `default`. If you're editing DataObjects -that are accessed with GraphQL in the CMS, you may have to build the `admin` schema as well. +that are accessed with GraphQL in the CMS, you may have to rebuild the `admin` schema as well. [/info] -Keep in mind that many of your changes will be in YAML, which also requires a flush. +Keep in mind that some of your changes will be in YAML in the `_config/` directory, which also +requires a flush. `vendor/bin/sake dev/graphql/build schema=default flush=1` -[info] -If you do not provide a `schema` parameter, the task will build all schemas. -[/info] - -### Building on dev/build +#### Building on dev/build -By default, all schemas will be built as a side-effect of `dev/build`. To disable this, change -the config: +By default, all schemas will be built during `dev/build`. To disable this, change the config: ```yaml SilverStripe\GraphQL\Extensions\DevBuildExtension: @@ -64,17 +76,17 @@ SilverStripe\GraphQL\Extensions\DevBuildExtension: ### Caching -Generating code is a pretty expensive process. A large schema with 50 dataobject classes exposing +Generating code is a pretty expensive process. A large schema with 50 `DataObject` classes exposing all their operations can take up to **20 seconds** to generate. This may be acceptable for initial builds and deployments, but during incremental development this can really slow things down. To mitigate this, the generated code for each type is cached against a signature. -If the type hasn't changed, it doesn't re-render. This reduces build times to **under one second** for incremental changes. +If the type hasn't changed, it doesn't get re-built. This reduces build times to **under one second** for incremental changes. -#### Clearing the cache +#### Clearing the schema cache -Normally, we'd use `flush=1` to clear the cache, but since you almost always need to run `flush=1` with the build task, it isn't a good fit. Instead, use `clear=1`. +If you want to completely re-generate your schema from scratch, you can add `clear=1` to the `dev/graphql/build` command. `vendor/bin/sake dev/graphql/build schema=default clear=1` @@ -83,31 +95,30 @@ of a caching issue. If the issue is resolved, record exactly what you changed an ### Build gotchas -Keep in mind that it's not always explicit schema configuration changes that require a build. +Keep in mind that it's not always explicit schema definition changes that require a build. Anything influencing the output of the schema will require a build. This could include tangential changes such as: -* Updating the `$db` array (or relationships) of a DataObject that has `fields: '*'`. +* Updating the `$db` array (or relationships) of a `DataObject` class that has `fields: '*'` (i.e. include all fields on that class in the schema). * Adding a new resolver for a type that uses [resolver discovery](../working_with_generic_types/resolver_discovery) -* Adding an extension to a DataObject -* Adding a new subclass to a DataObject that is already exposed -* If you are using Silverstripe CMS **without the [silverstripe/assets](https://github.com/silverstripe/silverstripe-assets) module installed, the build task will leave a `.graphql` file artefact in your public directory for CMS reference. -Though it doesn't contain any highly sensitive data, we recommend you block this file from being viewed by outside - traffic. - - +* Adding an extension to a `DataObject` class +* Adding a new subclass of a `DataObject` class that is already exposed ### Viewing the generated code -By default, the generated code is placed in the `.graphql-generated/` directory in the root of your project. -It is not meant to be accessible through your webserver (which is ensured by dot-prefixing) -and keeping it outside of the `public/` webroot. +By default, the generated PHP code is placed in the `.graphql-generated/` directory in the root of your project. +It is not meant to be accessible through your webserver, Which is ensured by keeping it outside of the +`public/` webroot and the inclusion of a `.htaccess` file in each schema folder. Additional files are generated for CMS operation in `public/_graphql/`, and -those are meant to be accessible through your webserver. +those _are_ meant to be accessible through your webserver. See [Tips and Tricks: Schema Introspection](tips_and_tricks#schema-introspection) to find out how to generate these files for your own schema. +[alert] +While it is safe for you to view these files, you should not manually alter them. If you need to make a change +to your GraphQL schema, you should [update the schema definition](configuring_your_schema) and rebuild your schema. +[/alert] ### Further reading diff --git a/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/06_using_procedual_code.md b/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/04_using_procedural_code.md similarity index 69% rename from docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/06_using_procedual_code.md rename to docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/04_using_procedural_code.md index 9e5f810a58b..9d5e35a0543 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/06_using_procedual_code.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/04_using_procedural_code.md @@ -1,6 +1,7 @@ --- title: Building a schema with procedural code summary: Use PHP code to build your schema +icon: tools --- # Getting started @@ -22,7 +23,8 @@ may have an enum containing a list of all the languages that are configured for wouldn't make sense to build this statically. It makes more sense to have a single source of truth. -Internally, model-driven types that conform to the shapes of their models must use procedural code to add fields, create operations, and more, because the entire premise of model-driven +Internally, model-driven types that conform to the shapes of their models must use procedural +code to add fields, create operations, and more, because the entire premise of model-driven types is that they're dynamic. So the procedural API for schemas has to be pretty robust. Lastly, if you just prefer writing PHP to writing YAML, this is a good option, too. @@ -35,7 +37,7 @@ the schema build. ### Adding executable code -We can use the `execute` section of the config to add an implementation of `SchemaUpdater`. +We can use the `execute` section of the config to add an implementation of [`SchemaUpdater`](api:SilverStripe\GraphQL\Schema\Interfaces\SchemaUpdater). ```yaml SilverStripe\GraphQL\Schema\Schema: @@ -46,7 +48,7 @@ SilverStripe\GraphQL\Schema\Schema: - 'MyProject\MySchema' ``` -Now just implement the `SilverStripe\GraphQL\Schema\Interfaces\SchemaUpdater` interface. +Now just implement the [`SchemaUpdater`](api:SilverStripe\GraphQL\Schema\Interfaces\SchemaUpdater) interface. **app/src/MySchema.php** ```php @@ -64,57 +66,56 @@ class MySchema implements SchemaUpdater ### Example code -Most the API should be self-documenting, and a good IDE should autocomplete everything you +Most of the API should be self-documenting, and a good IDE should autocomplete everything you need, but the key methods map directly to their configuration counterparts: -* types (`->addType(Type $type)`) -* models (`->addModel(ModelType $type)`) -* queries (`->addQuery(Query $query)`) -* mutations (`->addMutation(Mutation $mutation)`) -* enums (`->addEnum(Enum $type)`) -* interfaces (`->addInterface(InterfaceType $type)`) -* unions (`->addUnion(UnionType $type)`) - +* types (`$schema->addType(Type $type)`) +* models (`$schema->addModel(ModelType $type)`) +* queries (`$schema->addQuery(Query $query)`) +* mutations (`$schema->addMutation(Mutation $mutation)`) +* enums (`$schema->addEnum(Enum $type)`) +* interfaces (`$schema->addInterface(InterfaceType $type)`) +* unions (`$schema->addUnion(UnionType $type)`) ```php public static function updateSchema(Schema $schema): void { - $myType = Type::create('Country') + $countryType = Type::create('Country') ->addField('name', 'String') ->addField('code', 'String'); - $schema->addType($myType); + $schema->addType($countryType); - $myQuery = Query::create('readCountries', '[Country]') + $countriesQuery = Query::create('readCountries', '[Country]!') ->addArg('limit', 'Int'); + $schema->addQuery($countriesQuery); $myModel = $schema->createModel(MyDataObject::class) ->addAllFields() ->addAllOperations(); $schema->addModel($myModel); - } ``` -#### Fluent setters +#### Chainable setters To make your code chainable, when adding fields and arguments, you can invoke a callback to update it on the fly. ```php -$myType = Type::create('Country') +$countryType = Type::create('Country') ->addField('name', 'String', function (Field $field) { - // Must be a callable. No inline closures allowed! - $field->setResolver([MyClass::class, 'myResolver']) + $field->setResolver([MyResolverClass::class, 'countryResolver']) ->addArg('myArg', 'String!'); }) ->addField('code', 'String'); -$schema->addType($myType); +$schema->addType($countryType); -$myQuery = Query::create('readCountries', '[Country]') +$countriesQuery = Query::create('readCountries', '[Country]!') ->addArg('limit', 'Int', function (Argument $arg) { $arg->setDefaultValue(20); }); +$schema->addQuery($countriesQuery); ``` ### Further reading diff --git a/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/05_deploying_the_schema.md b/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/05_deploying_the_schema.md index 9582b9e42ba..d82349b62ed 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/05_deploying_the_schema.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/05_deploying_the_schema.md @@ -1,9 +1,12 @@ --- -title: Upgrade to GraphQL v4 -summary: Upgrade your Silverstripe CMS project to use graphQL version 4 +title: Deploying the schema +summary: Deploy your GraphQL schema to a test or production environment +icon: rocket --- -# Upgrading to GraphQL v4 +# Getting started + +[CHILDREN asList] [alert] You are viewing docs for a pre-release version of silverstripe/graphql (4.x). @@ -13,94 +16,59 @@ Docs for the current stable version (3.x) can be found [here](https://github.com/silverstripe/silverstripe-graphql/tree/3) [/alert] -Silverstripe CMS Recipe 4.11 defaults to installing silverstripe/graphql v4. Previous releases installed version 3. - -## What does silverstripe/graphql do and why are you changing this? - -GraphQL is a query language for APIs. It was initially designed by Facebook but it is now used widely across the internet by all sorts of organisations including GitHub, AirBnB, Lyft, PayPal, Shopify and Silverstripe CMS … to name just a few. - -`silverstripe/graphql` is an implementation of GraphQL specific to Silverstripe CMS. It is used to power some aspects of the CMS UI. It can also be used by developers to create APIs that other web services can use to read or update data in your CMS sites. This opens a lot of use cases like using Silverstripe CMS as “headless” CMS. - -Until the 4.10 release, Silverstripe CMS would default to using silverstripe/graphql v3. While silverstripe/graphql v3 was sufficient to support the basic CMS use cases it was being used for, it was not performant enough to build more complex applications. - -`silverstripe/graphql` v4 is a complete rewrite and provides substantial performance improvements. - -`silverstripe/graphql` v4 provides developers a first class tool for building APIs and allowing third party services to integrate with their Silverstripe CMS websites. - -## That sounds risky, do I absolutely have to use version 4? - -Silverstripe CMS has been shipping with dual support for `silverstripe/graphql` v3 and v4 since the 4.8 release. Until now `silverstripe/graphql` v4 had been in alpha and you had to explicitly opt-in to get it. At Silverstripe, we are already using `silverstripe/graphql` v4 in production on several projects. - -All the supported Silverstripe CMS modules that use `silverstripe/graphql` have dual-support for version 3 and version 4. If you wish to stay on `silverstripe/graphql` v3, you can do so and it will not block you from upgrading to Silverstripe CMS 4.11. +## Deploying the schema -We will maintain support for `silverstripe/graphql` v3 in Silverstripe CMS 4 for the foreseeable future. Any change to this policy will be announced at least 6 months in advance. +One way or another, you must get the `.graphql-generated/` and `public/_graphql/` folders into your test and production environments for Silverstripe CMS (and your own custom queries) to work as expected. There are many ways to do so. The options below are listed in order of complexity. -### Opting out of `silverstripe/graphql` v4 and sticking to version 3 +### Options for any hosting solution -If your project composer.json file already explicitly requires silverstripe/graphql, you don’t need to do anything. +#### Commit the schema to version control {#commit-to-vcs} -If your project uses silverstripe/recipe-cms, composer will try to install `silverstripe/graphq` v4.0 when you upgrade to the 4.11 release. To stay on silverstripe/graphql:^3, you’ll need to explicitly require `silverstripe/graphql` v3.8. +A simplistic approach is to build the schema in your local development environment and add the `.graphql-generated/` and `public/_graphql/` folders to your version control system. With this approach you would most likely want to disable schema generation at `dev/build`. -``` -composer require silverstripe/graphql:^3 -``` +This approach has the advantage of being very simple, but it will pollute your commits with massive diffs for the generated code. -To validate which version of `silverstripe/graphql` your project is using, run this composer command: -`composer show silverstripe/graphql` +#### Explicitly build the schema during each deployment {#build-during-deployment} -To view which dependencies require `silverstripe/graphql`, run this composer command: -`composer why silverstripe/graphql` +Many projects will automatically run a `dev/build` whenever they deploy a site to their production environment. If that’s your case, then you can just let this process run normally and generate the `.graphql-generated/` and `public/_graphql/` folders for you. This will allow you to add these folders to your `.gitignore` file and avoid tracking the folder in your version control system. -## How do I get this thing working? +Be aware that for this approach to work, the process executing the `dev/build` must have write access to create the folders (or you must create those folders yourself, and give write access for those folders specifically), and for multi-server environments a `dev/build` or `dev/graphql/build` must be executed on each server hosting your site after each deployment. -Part of the reason why `silverstripe/graphql` v4 is so much faster than v3 is that it has a “code generation” step. Silverstripe CMS will generate PHP classes for your GraphQL schemas and stores them in a `.graphql-generated` folder in the root of your project. +#### Use a CI/CD pipeline to build your schema {#using-ci-cd} -### What triggers a GraphQL code build? - -- This folder will automatically be generated when you run a `dev/build` on your project. -- You can also run the `dev/graphql/build` command to explicitly build your GraphQL schemas. -- Silverstripe CMS will attempt to generate your schema on the first graphql request if it wasn’t already generated. - -### Deploying your `.graphql-generated` folder - -One way or another, you must get this `.graphql-generated` folder into your production environment for Silverstripe CMS to work as expected. There are many ways to do so. - -#### Commit `.graphql-generated` - -A simplistic approach is to build the `.graphql-generated` in your local development environment and add it to your source control system. - -This approach has the advantage of being very simple, however it will pollute your commits with massive diff for the generated code. +Projects with more sophisticated requirements or bigger schemas exposing more than 100 `DataObject` classes may want to consider using a continuous-integration/continuous-deployment (CI/CD) pipeline to build their GraphQL schema. -#### Run a dev/build on each deployment -Many projects will automatically run a dev/build whenever they deploy a site to their production environment. If that’s your case, then you can just let this process run normally and generate the `.graphql-generated` folder for you. This will allow you to add `.graphql-generated` to your `.gitignore` file and avoid tracking the folder in your source control system. +In this kind of setup, you would need to update your deployment script to run the `dev/graphql/build` command which builds the `.graphql-generated/` and `public/_graphql/` folders. In multi-server environments this must be executed on each server hosting your site. -Be aware that for this approach to work, the process executing the `dev/build` must have write access to create the `.graphql-generated` folder and a `dev/build` or `dev/graphql/build` must be executed on each server hosting your site after each deployment. +### Multi-server hosting solutions {#multi-server} -For example, if your site is hosted in an environment with multiple servers and you only run a dev/build on one single server, then the other servers won’t have a `.graphql-generated` folder. This might also impact you if your project is hosted in an environment configured to auto-scale with demand. +If your site is hosted in an environment with multiple servers or configured to auto-scale with demand, there are some additional considerations. For example if you only generate the schema on one single server, then the other servers won’t have a `.graphql-generated/` or `public/_graphql/` folder (or those folders will be empty if you manually created them). -Alternatively, you could configure a process to sync your `.graphql-generated` folder across all your servers. In that case you only need to run `dev/build` or `dev/graphql/build` on the server with the original folder. +#### Rely on "on-demand" schema generation on the first GraphQL request {#on-demand} -#### Rely on “on-demand” schema generation on the first GraphQL request -When the first GraphQL schema request occurs, Silverstripe CMS will attempt to build the `.graphql-generated` folder “on-demand” if it’s not already present on the server. This will impose a one-time hit on the first graphQL request. If your project defines multiple schemas, only the schema that is being accessed will be generated. +When the first GraphQL schema request occurs, `silverstripe/graphsl` will attempt to build the `.graphql-generated/` and `public/_graphql/` folders "on-demand" if they're not already present on the server. Similarly, if the folders are present but empty, it will build the schema "on-demand". This will impose a one-time performance hit on the first GraphQL request. If your project defines multiple schemas, only the schema that is being accessed will be generated. For most common use cases, this process is relatively fast. For example, the GraphQL schema that is used to power the CMS can be built in about a quarter of a second. While benchmarking schema generation performance, we measured that a schema exposing 180 DataObjects with 1600 relations could be built on-demand in less than 6 seconds on a small AWS instance. -Our expectation is that on-demand schema generation will be appropriate for most projects with small or medium schemas. +Our expectation is that on-demand schema generation will be performant for most projects with small or medium schemas. -#### Use a CI/CD pipeline to build your schema +[warning] +Note that with this approach you will need to remove or empty the `.graphql-generated/` and `public/_graphql/` folders on each server for each deployment that includes a change to the schema definition, or you risk having an outdated GraphQL schema. The "on-demand" schema generation does not detect changes to the schema definition. +[/warning] -Projects with more sophisticated requirements or bigger schemas exposing more than 100 `DataObject` classes may want to consider using a continuous-integration/continuous-deployment (CI/CD) pipeline to build their GraphQL schema. +#### Build the schema during/before deployment and share it across your servers {#multi-server-shared-dirs} + +If you have a particularly large schema, you may want to ensure it is always built before the first GraphQL request. It might make sense for you to sync your `.graphql-generated/` and `public/_graphql/` folders across all your servers using an EFS or similar mechanism. In that case you only need to run `dev/build` or `dev/graphql/build` on the server with the original folder - but bear in mind that this may have a performance impact. -In this kind of setup, you would need to update your deployment script to run the `dev/graphql/build` command to build the `.graphql-generated` folder. +### Performance considerations when building the GraphQL schema {#performance-considerations} -## Performance considerations when building graphQL schema -The main driver in the resources it takes to build a GraphQL schema is the number DataObjects and the number of exposed relations in that schema. In most cases, not all DataObject in your database will be included in your schema. DataObjects not included in your schema will not impact the time or memory needed to build it. +The main driver in the resources it takes to build a GraphQL schema is the number DataObjects and the number of exposed relations in that schema. In most cases, not all DataObjects in your database will be included in your schema - best practice is to only add classes to your schema definition if you will need to query them. DataObjects not included in your schema will not impact the time or memory needed to build it. -Silverstripe CMS defines an “admin” schema it uses for its own purpose. This schema is relatively small and has a negligible performance impact. +Silverstripe CMS defines an "admin" schema it uses for its own purpose. This schema is relatively small and has a negligible performance impact. -As an indication, we ran some benchmarks on a t3.micro AWS instance. Those numbers may not be representative of the performance in your own environment. If you intend to build large graphQL schemas, you should take the time to run your own benchmarks and adjust your deployment strategy accordingly. +As an indication, we ran some benchmarks on a t3.micro AWS instance. The results are in the table below. These numbers may not be representative of the performance in your own environment. If you intend to build large GraphQL schemas, you should take the time to run your own benchmarks and adjust your deployment strategy accordingly. DataObjects in schema | Build time (ms) | Memory use (MB) -- | -- | -- @@ -111,15 +79,14 @@ DataObjects in schema | Build time (ms) | Memory use (MB) 250 | 5070 | 114 500 | 11,540 | 208 -## Gotchas +### Gotchas -### Permissions of the `.graphql-generated` folder -The process that is generating the `.graphql-generated` folder must have write permissions to create the folder and to update existing files. If different users are used to generate the `.graphql-generated` folder, then you must make sure that each user retains write access on the folder. +#### Permissions of the `.graphql-generated/` and `public/_graphql/` folders {#gotchas-permissions} -For example, if you manually run a `dev/build` under a foobar user, `.graphql-generated` folder will be owned by foobar. If your web server is running under the www-data user and you try to call `dev/graphql/build` in your browser, you might get an error if www-data doesn’t have write access. +The process that is generating these folders must have write permissions to create the folder and to update existing files. If different users are used to generate the folders, then you must make sure that each user retains write access on them. -### Tracking or ignoring the `.graphql-generated` folder +For example, if you manually run a `dev/build` under a foobar user, the folders will be owned by foobar. If your web server is running under the www-data user and you try to call `dev/graphql/build` in your browser, you might get an error if www-data doesn’t have write access. -Existing projects will not have an entry in their `.gitignore` file for `.graphql-generated`. If you do not want to track the `.graphql-generated` folder, you’ll have to manually add this entry to your `.gitignore`. +### Further reading -The `.gitignore` file in `silverstripe/installer` 4.11 has been updated to ignore the `.graphql-generated` folder. If you start a new project from `silverstripe/installer` 4.11.0 and want to track the `.graphql-generated` folder, you’ll have to update your `.gitignore` file. +[CHILDREN] diff --git a/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/index.md b/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/index.md index d92c904582c..2304e0c7559 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/index.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/01_getting_started/index.md @@ -7,19 +7,7 @@ icon: rocket # Getting started This section of the documentation will give you an overview of how to get a simple GraphQL API -up and running with some dataobject content. - -## Installing on silverstripe/recipe-cms < 4.11 - -The 4.8 - 4.10 releases of `recipe-cms` support both versions `3` and `4.0.0-alpha` versions of this module. Using the alpha (or beta) releases requires inlining the recipe and updating the `silverstripe/graphql` version. - -You can inline silverstripe/recipe-cms by running this command: - -``` -composer update-recipe silverstripe/recipe-cms -``` - -Alternatively, you can remove `silverstripe/recipe-cms` from your root `composer.json` and replace it with the contents of the `composer.json` in `silverstripe/recipe-cms`. +up and running with some `DataObject` content. [alert] You are viewing docs for a pre-release version of silverstripe/graphql (4.x). diff --git a/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/01_adding_dataobjects_to_the_schema.md b/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/01_adding_dataobjects_to_the_schema.md index 49baa649196..264046eb02a 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/01_adding_dataobjects_to_the_schema.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/01_adding_dataobjects_to_the_schema.md @@ -1,6 +1,6 @@ --- title: Adding DataObjects to the schema -summary: An overview of how the DataObject model can influence the creation of types, queries, and mutations +summary: An overview of how the `DataObject` model can influence the creation of types, queries, and mutations --- # Working with DataObjects @@ -15,14 +15,15 @@ Docs for the current stable version (3.x) can be found [here](https://github.com/silverstripe/silverstripe-graphql/tree/3) [/alert] -## The DataObject model type +## The `DataObject` model type -In Silverstripe CMS projects, our data tends to be contained in dataobjects almost exclusively, -and the silverstripe-graphql schema API is designed to make adding dataobject content fast and simple. +In Silverstripe CMS projects, our data tends to be contained in DataObjects almost exclusively, +and the `silverstripe/graphql` schema API is designed so that adding `DataObject` content to your +GraphQL schema definition is fast and simple. ### Using model types -While it is possible to add dataobjects to your schema as generic types under the `types` +While it is possible to add DataObjects to your schema as generic types under the `types` section of the configuration, and their associated queries and mutations under `queries` and `mutations`, this will lead to a lot of boilerplate code and repetition. Unless you have some really custom needs, a much better approach is to embrace _convention over configuration_ @@ -47,15 +48,16 @@ The class `Page` is a subclass of `DataObject`, so the bundled model type will kick in here and provide a lot of assistance in building out this part of our API. Case in point, by supplying a value of `*` for `fields` , we're saying that we want _all_ of the fields -on site tree. This includes the first level of relationships, as well, as defined on `has_one`, `has_many`, +on the `Page` class. This includes the first level of relationships, as defined on `has_one`, `has_many`, or `many_many`. [notice] Fields on relationships will not inherit the `*` fields selector, and will only expose their ID by default. +To add additional fields for those relationships you will need to add the corresponding `DataObject` model types. [/notice] The `*` value on `operations` tells the schema to create all available queries and mutations - for the dataobject, including: + for the DataObject, including: * `read` * `readOne` @@ -63,15 +65,16 @@ The `*` value on `operations` tells the schema to create all available queries a * `update` * `delete` -Now that we've changed our schema, we need to build it using the `dev/graphql/build` task: +Now that we've changed our schema, we need to build it using the `dev/graphql/build` command: -`$ vendor/bin/sake dev/graphql/build schema=default` +`vendor/bin/sake dev/graphql/build schema=default` -Now, we can access our schema on the default graphql endpoint, `/graphql`. +Now we can access our schema on the default graphql endpoint, `/graphql`. Test it out! -A query: +**A query:** + ```graphql query { readPages { @@ -90,17 +93,23 @@ query { } } } + } } ``` [info] Note the use of the default arguments on `date`. Fields created from `DBFields` -generate their own default sets of arguments. For more information, see the -[DBFieldArgs](query_plugins#dbfieldargs) for more information. +generate their own default sets of arguments. For more information, see +[DBFieldArgs](query_plugins#dbfieldargs). +[/info] + +[info] +The `... on BlogPage` syntax is called an [inline fragment](https://graphql.org/learn/queries/#inline-fragments). +You can learn more about this syntax in the [Inheritance](../inheritance) section. [/info] +**A mutation:** -A mutation: ```graphql mutation { createPage(input: { @@ -112,9 +121,9 @@ mutation { } ``` -[info] +[hint] Did you get a permissions error? Make sure you're authenticated as someone with appropriate access. -[/info] +[/hint] ### Configuring operations @@ -122,12 +131,18 @@ You may not always want to add _all_ operations with the `*` wildcard. You can a want by setting them to `true` (or `false` to remove them). **app/_graphql/models.yml** -``` +```yaml Page: fields: '*' operations: read: true create: true + +MyProject\Models\Product: + fields: '*' + operations: + '*': true + delete: false ``` Operations are also configurable, and accept a nested map of config. @@ -144,11 +159,11 @@ Page: #### Customising the input types -The input types, specifically in `create` and `update` can be customised with a -list of fields, which can include explicitly _disallowed_ fields. +The input types, specifically in `create` and `update`, can be customised with a +list of fields. The list can include explicitly _disallowed_ fields. **app/_graphql/models.yml** -``` +```yaml Page: fields: '*' operations: @@ -159,18 +174,19 @@ Page: update: fields: '*': true - sensitiveField: false + immutableField: false ``` ### Adding more fields -Let's add some more dataobjects, but this time, we'll only add a subset of fields and operations. +Let's add some more DataObjects, but this time, we'll only add a subset of fields and operations. -*app/_graphql/models.yml* +**app/_graphql/models.yml** ```yaml Page: fields: '*' operations: '*' + MyProject\Models\Product: fields: onSale: true @@ -178,6 +194,7 @@ MyProject\Models\Product: price: true operations: delete: true + MyProject\Models\ProductCategory: fields: title: true @@ -190,20 +207,21 @@ A couple things to note here: * By assigning a value of `true` to the field, we defer to the model to infer the type for the field. To override that, we can always add a `type` property: ```yaml -onSale: - type: Boolean +MyProject\Models\Product: + fields: + onSale: + type: Boolean ``` -* The mapping of our field names to the DataObject property is case-insensitive. It is a +* The mapping of our field names to the `DataObject` property is case-insensitive. It is a convention in GraphQL APIs to use lowerCamelCase fields, so this is given by default. - [/notice] ### Bulk loading models -It's likely that in your application, you have a whole collection of classes you want exposed to the API, with roughly +It's likely that in your application you have a whole collection of classes you want exposed to the API with roughly the same fields and operations exposed on them. It can be really tedious to write a new declaration for every single -dataobject in your project, and as you add new ones, there's a bit of overhead in remembering to add it to the +`DataObject` in your project, and as you add new ones, there's a bit of overhead in remembering to add it to the GraphQL schema. Common use cases might be: @@ -216,11 +234,10 @@ Common use cases might be: You can create logic like this using the `bulkLoad` configuration file, which allows you to specify groups of directives that load a bundle of classes and apply the same set of configuration to all of them. - **_graphql/bulkLoad.yml** ```yaml elemental: # An arbitrary key to define what these directives are doing - # Load all content blocks + # Load all elemental blocks except MySecretElement load: inheritanceLoader: include: @@ -235,7 +252,8 @@ elemental: # An arbitrary key to define what these directives are doing read: true readOne: true app: - # Load everything in our app that has the Versioned extension + # Load everything in our MyApp\Models\ namespace that has the Versioned extension + # unless the filename ends with .secret.php load: namespaceLoader: include: @@ -256,6 +274,7 @@ app: By default, four loaders are provided to you to help gather specific classnames: #### By namespace + * **Identifier**: `namespaceLoader` * **Description**: Include or exclude classes based on their namespace * **Example**: `include: [MyApp\Models\*]` @@ -275,10 +294,10 @@ By default, four loaders are provided to you to help gather specific classnames: #### By filepath * **Identifier**: `filepathLoader` -* **Description**: Include or exclude any classes in files matching a given glob expression, relative to the base path. Module syntax is allowed. +* **Description**: Include or exclude any classes in files matching a given glob expression, relative to the base path. Module syntax is allowed. * **Examples**: - - `include: [ 'src/models/*.model.php' ]` - - `include: [ 'somevendor/somemodule: src/Models/*.php' ]` + * `include: [ 'src/models/*.model.php' ]` + * `include: [ 'somevendor/somemodule: src/Models/*.php' ]` Each block starts with a collection of all classes that gets filtered as each loader runs. The primary job of a loader is to _remove_ classes from the entire collection, not add them in. @@ -289,13 +308,13 @@ of a loader is to _remove_ classes from the entire collection, not add them in. [info] If you find that this paints with too big a brush, you can always override individual models explicitly in `models.yml`. -The bulk loaders run _before_ the models.yml config is loaded. +The bulk loaders run _before_ the `models.yml` config is loaded. [/info] -#### DataObjects subclasses are the default starting point +#### `DataObject` subclasses are the default starting point -Because this is Silverstripe CMS, and it's likely that you're using dataobject models only, the bulk loaders start with an -initial filter, which is defined as follows: +Because this is Silverstripe CMS, and it's likely that you're using `DataObject` models only, the bulk loaders start with an +initial filter which is defined as follows: ```yaml inheritanceLoader: @@ -303,8 +322,8 @@ inheritanceLoader: - SilverStripe\ORM\DataObject ``` -This ensures that at a bare minimum, you're always filtering by dataobject classes only. If, for some reason, you -have a non-dataobject class in `App\Models\*`, it will automatically be filtered out due to this default setting. +This ensures that at a bare minimum, you're always filtering by `DataObject` classes _only_. If, for some reason, you +have a non-`DataObject` class in `App\Models\*`, it will automatically be filtered out due to this default setting. This default is configured in the `defaultBulkLoad` setting in your schema config. Should you ever want to disable that, just set it to `false`. @@ -316,39 +335,40 @@ defaultBulkLoad: false #### Creating your own bulk loader -Bulk loaders must extend `SilverStripe\GraphQL\Schema\BulkLoader\AbstractBulkLoader`. They need to declare an -identifier (e.g. `namespaceLoader`) to be referenced in the config, and they must provide a -`collect(Collection $collection): Collection` which returns a new `Collection` instance once the loader has done its -work parsing through the `include` and `exclude` directives. +Bulk loaders must extend [`AbstractBulkLoader`](api:SilverStripe\GraphQL\Schema\BulkLoader\AbstractBulkLoader). They +need to declare an identifier (e.g. `namespaceLoader`) to be referenced in the config, and they must implement +[`collect()`](api:SilverStripe\GraphQL\Schema\BulkLoader\AbstractBulkLoader::collect()) which returns a new `Collection` +instance once the loader has done its work parsing through the `include` and `exclude` directives. Bulk loaders are automatically registered. Just creating the class is all you need to do to have it available for use in your `bulkLoad.yml` file. - ### Customising model fields You don't have to rely on the model to tell you how fields should resolve. Just like generic types, you can customise them with arguments and resolvers. -*app/_graphql/models.yml* +**app/_graphql/models.yml** ```yaml MyProject\Models\Product: fields: title: type: String - resolver: [ 'MyProject\Resolver', 'resolveSpecialTitle' ] + resolver: ['MyProject\Resolver', 'resolveSpecialTitle'] 'price(currency: String = "NZD")': true ``` -For more information on custom arguments and resolvers, see the [adding arguments](../working_with_generic_types/adding_arguments) and [resolver discovery](../working_with_generic_types/resolver_discovery) documentation. +For more information on custom arguments and resolvers, see the +[adding arguments](../working_with_generic_types/adding_arguments) and +[resolver discovery](../working_with_generic_types/resolver_discovery) documentation. ### Excluding or customising "*" declarations -You can use the `*` as a field or operation, and anything that follows it will override the +You can use `*` as a field or operation, and anything that follows it will override the all-inclusive collection. This is almost like a spread operator in Javascript: ```js -const newObj = {...oldObj, someProperty: 'custom' } +const newObj = {...oldObj, someProperty: 'custom'} ``` Here's an example: @@ -383,8 +403,8 @@ This block list applies for all operations (read, update, etc). **app/_config/graphql.yml** ```yaml SilverStripe\CMS\Model\SiteTree: - graphql_blacklisted_fields: - myPreviewTokenField: true + graphql_blacklisted_fields: + myPreviewTokenField: true ``` ### Model configuration @@ -396,10 +416,10 @@ subsection. ### Customising the type name -Most DataObject classes are namespaced, so converting them to a type name ends up +Most `DataObject` classes are namespaced, so converting them to a type name ends up being very verbose. As a default, the `DataObjectModel` class will use the "short name" -of your DataObject as its typename (see: `ClassInfo::shortName()`). That is, -`MyProject\Models\Product` becomes `Product`. +of your `DataObject` as its typename (see: [`ClassInfo::shortName()`](api:SilverStripe/Core/ClassInfo::shortName())). +That is, `MyProject\Models\Product` becomes `Product`. Given the brevity of these type names, it's not inconceivable that you could run into naming collisions, particularly if you use feature-based namespacing. Fortunately, there are @@ -420,10 +440,6 @@ when the type name is derived from the class name. The most case for this is the `Page` class, which is both at the root namespace and often in your app namespace, e.g. `MyApp\Models\Page`. - - - - #### The type formatter The `type_formatter` is a callable that can be set on the `DataObjectModel` config. It takes @@ -435,15 +451,15 @@ Let's turn `MyProject\Models\Product` into the more specific `MyProjectProduct` ```yaml modelConfig: DataObject: - type_formatter: ['MyProject\Formatters', 'formatType' ] + type_formatter: ['MyProject\GraphQL\Formatter', 'formatType'] ``` [info] -In the above example, `DataObject` is the result of the `DataObjectModel::getIdentifier()`. Each -model class must declare one of these. +In the above example, `DataObject` is the result of [`DataObjectModel::getIdentifier()`](api:SilverStripe\GraphQL\Schema\DataObject::getIdentifier()). +Each model class must declare one of these. [/info] -Your formatting function could look something like: +The formatting function in your `MyProject\GraphQL\Formatter` class could look something like: ```php public static function formatType(string $className): string @@ -461,16 +477,19 @@ public static function formatType(string $className): string #### The type prefix -You can also add prefixes to all your DataObject types. This can be a scalar value or a callable, +You can also add prefixes to all your `DataObject` types. This can be a scalar value or a callable, using the same signature as `type_formatter`. -*app/_graphql/config.yml* +**app/_graphql/config.yml** ```yaml modelConfig: - DataObject + DataObject: type_prefix: 'MyProject' ``` +This would automatically set the type name for your `MyProject\Models\Product` class to `MyProjectProduct` +without needing to declare a `type_formatter`. + ### Further reading [CHILDREN] diff --git a/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/02_query_plugins.md b/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/02_query_plugins.md index 53826c7625e..9a67e7ec37c 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/02_query_plugins.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/02_query_plugins.md @@ -1,6 +1,6 @@ --- -title: DataObject query plugins -summary: Learn about some of the useful goodies that come pre-packaged with DataObject queries +title: `DataObject` query plugins +summary: Learn about some of the useful goodies that come pre-packaged with `DataObject` queries --- # Working with DataObjects @@ -15,11 +15,11 @@ Docs for the current stable version (3.x) can be found [here](https://github.com/silverstripe/silverstripe-graphql/tree/3) [/alert] -## DataObject query plugins +## `DataObject` query plugins This module has a [plugin system](../plugins) that affords extensibility to queries, mutations, types, fields, and just about every other thread of the schema. Model types can define default -plugins to include, and for DataObject queries, these include: +plugins to include, and for `DataObject` queries, these include: * filter * sort @@ -172,7 +172,7 @@ it is not possible to filter by fields with custom resolvers. #### Customising the filter fields -By default, all fields on the dataobject, including relationships, are included. To customise +By default, all fields on the DataObject, including relationships, are included. To customise this, just add a `fields` config to the plugin definition: *app/_graphql/models.yml* @@ -252,7 +252,7 @@ query { #### Customising the sort fields -By default, all fields on the dataobject, including `has_one` relationships, are included. +By default, all fields on the DataObject, including `has_one` relationships, are included. To customise this, just add a `fields` config to the plugin definition: *app/_graphql/models.yml* @@ -382,7 +382,7 @@ it will be reused rather than proceeding to the deduplication strategy. You can specify custom enum names in the plugin config: -``` +```yaml modelConfig: DataObject: plugins: @@ -396,7 +396,7 @@ modelConfig: You can also specify enums to be ignored. (`ClassName` does this on all DataObjects to prevent inheritance issues) -``` +```yaml modelConfig: DataObject: plugins: @@ -410,7 +410,7 @@ modelConfig: ### The getByLink plugin When the `silverstripe/cms` module is installed (it is in most cases), a plugin called `getByLink` -will ensure that queries that return a single DataObject model (e.g. readOne) get a new query argument +will ensure that queries that return a single `DataObject` model (e.g. readOne) get a new query argument called `link` (configurable on the `field_name` property of `LinkablePlugin`). ```graphql diff --git a/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/03_permissions.md b/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/03_permissions.md index bedb0e42472..e6ae12cd90a 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/03_permissions.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/03_permissions.md @@ -1,6 +1,6 @@ --- -title: DataObject operation permissions -summary: A look at how permissions work for DataObject queries and mutations +title: `DataObject` operation permissions +summary: A look at how permissions work for `DataObject` queries and mutations --- # Working with DataObjects @@ -15,7 +15,7 @@ Docs for the current stable version (3.x) can be found [here](https://github.com/silverstripe/silverstripe-graphql/tree/3) [/alert] -## DataObject operation permissions +## `DataObject` operation permissions Any of the operations that come pre-configured for DataObjects are secured by the appropriate permissions by default. @@ -41,7 +41,7 @@ Query permissions are a bit more complicated, because they can either be in list or a single item. Rather than throw, these permission checks work as filters. [notice] -It is critical that you have a `canView()` method defined on your dataobjects. Without this, only admins are +It is critical that you have a `canView()` method defined on your DataObjects. Without this, only admins are assumed to have permission to view a record. [/notice] diff --git a/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/04_inheritance.md b/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/04_inheritance.md index 9834e866f01..88cfa79ed54 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/04_inheritance.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/04_inheritance.md @@ -1,6 +1,6 @@ --- -title: DataObject inheritance -summary: Learn how inheritance is handled in DataObject types +title: `DataObject` inheritance +summary: Learn how inheritance is handled in `DataObject` types --- # Working with DataObjects @@ -15,12 +15,12 @@ Docs for the current stable version (3.x) can be found [here](https://github.com/silverstripe/silverstripe-graphql/tree/3) [/alert] -## DataObject inheritance +## `DataObject` inheritance The inheritance pattern used in the ORM is a tricky thing to navigate in a GraphQL API, mostly owing to the fact that there is no concept of inheritance in GraphQL types. The main tools we have at our -disposal are [interfaces](https://graphql.org/learn/schema/#interfaces) and [unions](https://graphql.org/learn/schema/#union-types) to deal with this type of architecture, and we leverage both of them when -working with dataobjects. +disposal are [interfaces](https://graphql.org/learn/schema/#interfaces) and [unions](https://graphql.org/learn/schema/#union-types) +to deal with this type of architecture, and we leverage both of them when working with DataObjects. ### Key concept: Querying types that have descendants @@ -67,7 +67,8 @@ query { ``` But what about when we want more than `title` and `content`? In some cases, we'll want fields that are specific to -`BlogPage`. When accessing fields for a specific implementation, we need to use an [inline fragment](https://graphql.org/learn/queries/#inline-fragments) to select them. +`BlogPage`. When accessing fields for a specific implementation, we need to use an [inline fragment](https://graphql.org/learn/queries/#inline-fragments) +to select them. ```graphql query { @@ -91,7 +92,7 @@ query { } ``` -So the fields that are common to every possible type in the result set can be directly selected (with no `...on` +So the fields that are common to every possible type in the result set can be directly selected (with no `...on` syntax), because they're part of the common interface. They're guaranteed to exist on every type. But for fields that only appear on some types, we need to be explicit. @@ -445,7 +446,7 @@ class DigitalProduct extends Product Now our query breaks: -``` +```graphql query { readProducts { nodes { @@ -457,7 +458,7 @@ query { We need to revise it: -``` +```graphql query { readProducts { nodes { diff --git a/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/05_versioning.md b/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/05_versioning.md index cf004fac4e4..b770f40b0e4 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/05_versioning.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/05_versioning.md @@ -17,7 +17,7 @@ Docs for the current stable version (3.x) can be found ## Versioned content -For the most part, if your DataObject has the `Versioned` extension applied, there is nothing you need to do +For the most part, if your `DataObject` has the `Versioned` extension applied, there is nothing you need to do explicitly, but be aware that it will affect the operations and fields of your type. You can also [disable](#disable) versioning for your schema if you don't need it. @@ -26,7 +26,7 @@ You can also [disable](#disable) versioning for your schema if you don't need it There are several plugins provided by the `silverstripe-versioned` module that affect how versioned DataObjects appear in the schema. These include: -* The `versioning` plugin, applied to the DataObject type +* The `versioning` plugin, applied to the `DataObject` type * The `readVersion` plugin, applied to the queries for the DataObject * The `unpublishOnDelete` plugin, applied to the delete mutation @@ -35,11 +35,11 @@ Let's walk through each one. #### The `versioning` plugin Defined in the `SilverStripe\Versioned\GraphQL\Plugins\VersionedDataObject` class, this plugin adds -several fields to the DataObject type, including: +several fields to the `DataObject` type, including: ##### The `version` field -The `version` field on your DataObject will include the following fields: +The `version` field on your `DataObject` will include the following fields: * `author`: Member (Object -- the author of the version) * `publisher`: Member (Object -- the publisher of the version) @@ -65,7 +65,7 @@ query readPages { ##### The `versions` field -The `versions` field on your DataObject will return a list of the `version` objects described above. +The `versions` field on your `DataObject` will return a list of the `version` objects described above. The list is sortable by version number, using the `sort` parameter. ```graphql diff --git a/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/06_property_mapping.md b/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/06_property_mapping.md index 16c5f39076f..caf4e3f0140 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/06_property_mapping.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/06_property_mapping.md @@ -3,7 +3,6 @@ title: Property mapping and dot syntax summary: Learn how to customise field names, use dot syntax, and use aggregate functions --- - # Working with DataObjects [CHILDREN asList] @@ -18,7 +17,7 @@ Docs for the current stable version (3.x) can be found ## Property mapping and dot syntax -For the most part, field names are inferred through the DataObject model, but its API affords developers full +For the most part, field names are inferred through the `DataObject` model, but its API affords developers full control over naming: *app/_graphql/models.yml* diff --git a/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/index.md b/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/index.md index 31a44563da2..6bb1d45d6d2 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/index.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/02_working_with_dataobjects/index.md @@ -1,22 +1,22 @@ --- title: Working with DataObjects -summary: Add DataObjects to your schema, expose their fields, read/write operations, and more +summary: Add DataObjects to your schema, expose their fields, add read/write operations, and more icon: database --- # Working with DataObjects In this section of the documentation, we'll cover adding DataObjects to the schema, exposing their fields, -and adding read/write operations. Additionally, we'll cover some of the plugins that are available to DataObjects +and adding read/write operations. We'll also look at some of the plugins that are available to DataObjects like [sorting, filtering, and pagination](query_plugins), as well as some more advanced concepts like [permissions](permissions), [inheritance](inheritance) and [property mapping](property_mapping). -[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](https://www.silverstripe.org/blog/community-slack-channel/), -and report any issues at [github.com/silverstripe/silverstripe-graphql](https://github.com/silverstripe/silverstripe-graphql). +and report any issues at [github.com/silverstripe/silverstripe-graphql](https://github.com/silverstripe/silverstripe-graphql). Docs for the current stable version (3.x) can be found [here](https://github.com/silverstripe/silverstripe-graphql/tree/3) [/alert] + +[CHILDREN] diff --git a/docs/en/02_Developer_Guides/19_GraphQL/03_working_with_generic_types/01_creating_a_generic_type.md b/docs/en/02_Developer_Guides/19_GraphQL/03_working_with_generic_types/01_creating_a_generic_type.md index ff9e848f967..74eba124664 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/03_working_with_generic_types/01_creating_a_generic_type.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/03_working_with_generic_types/01_creating_a_generic_type.md @@ -31,7 +31,7 @@ Country: We've defined a type called `Country` that has two fields: `code` and `name`. An example record could be something like: -``` +```php [ 'code' => 'bt', 'name' => 'Bhutan' diff --git a/docs/en/02_Developer_Guides/19_GraphQL/03_working_with_generic_types/02_building_a_custom_query.md b/docs/en/02_Developer_Guides/19_GraphQL/03_working_with_generic_types/02_building_a_custom_query.md index 382fbe5e729..7e92d919f66 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/03_working_with_generic_types/02_building_a_custom_query.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/03_working_with_generic_types/02_building_a_custom_query.md @@ -19,7 +19,7 @@ Docs for the current stable version (3.x) can be found We've now defined the shape of our data, now we need to build a way to access it. For this, we'll need a query. Let's add one to the `queries` section of our config. -*app/_graphql/schema.yml* +**app/_graphql/schema.yml** ```yaml types: Country: @@ -72,7 +72,7 @@ other than that they **must be static methods**. You'll see why when we add it t Now, we just have to build the schema: -`$ vendor/bin/sake dev/graphql/build schema=default` +`vendor/bin/sake dev/graphql/build schema=default` Let's test this out in our GraphQL IDE. If you have the [graphql-devtools](https://github.com/silverstripe/silverstripe-graphql-devtools) module installed, just open it up and set it to the `/graphql` endpoint. @@ -109,7 +109,7 @@ And the expected response: ``` [notice] -Keep in mind that [plugins](../02_working_with_dataobjects/02_query_plugins.md) +Keep in mind that [plugins](../working_with_DataObjects/query_plugins) don't apply in this context. Most importantly, this means you need to implement your own `canView()` checks. [/notice] @@ -119,15 +119,15 @@ implement your own `canView()` checks. A resolver is executed in a particular query context, which is passed into the method as arguments. - * `$value`: An optional `mixed` value of the parent in your data graph. - Defaults to `null` on the root level, but can be useful to retrieve the object - when writing field-specific resolvers (see [Resolver Discovery](resolver_discovery)) - * `$args`: An array of optional arguments for this field (which is different from the [Query Variables](https://graphql.org/learn/queries/#variables)) - * `$context`: An arbitrary array which holds information shared between resolvers. - Use implementors of `SilverStripe\GraphQL\Schema\Interfaces\ContextProvider` to get and set - data, rather than relying on the array keys directly. - * `$info`: Data structure containing useful information for the resolving process (e.g. the field name). - See [Fetching Data](http://webonyx.github.io/graphql-php/data-fetching/) in the underlying PHP library for details. +* `$value`: An optional `mixed` value of the parent in your data graph. + Defaults to `null` on the root level, but can be useful to retrieve the object + when writing field-specific resolvers (see [Resolver Discovery](resolver_discovery)) +* `$args`: An array of optional arguments for this field (which is different from the [Query Variables](https://graphql.org/learn/queries/#variables)) +* `$context`: An arbitrary array which holds information shared between resolvers. + Use implementors of `SilverStripe\GraphQL\Schema\Interfaces\ContextProvider` to get and set + data, rather than relying on the array keys directly. +* `$info`: Data structure containing useful information for the resolving process (e.g. the field name). + See [Fetching Data](http://webonyx.github.io/graphql-php/data-fetching/) in the underlying PHP library for details. ## Using Context Providers diff --git a/docs/en/02_Developer_Guides/19_GraphQL/03_working_with_generic_types/03_resolver_discovery.md b/docs/en/02_Developer_Guides/19_GraphQL/03_working_with_generic_types/03_resolver_discovery.md index 4e8c89907ba..59e3c64fe10 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/03_working_with_generic_types/03_resolver_discovery.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/03_working_with_generic_types/03_resolver_discovery.md @@ -127,7 +127,7 @@ Now that we're using logic to discover our resolver, we can clean up the config Re-run the schema build, with a flush, and let's go! -`$ vendor/bin/sake dev/graphql/build schema=default flush=1` +`vendor/bin/sake dev/graphql/build schema=default flush=1` ### Field resolvers diff --git a/docs/en/02_Developer_Guides/19_GraphQL/03_working_with_generic_types/index.md b/docs/en/02_Developer_Guides/19_GraphQL/03_working_with_generic_types/index.md index d8373c403eb..0fad422bcf5 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/03_working_with_generic_types/index.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/03_working_with_generic_types/index.md @@ -1,21 +1,18 @@ --- title: Working with generic types -summary: Break away from the magic of DataObject models and build types and queries from scratch. +summary: Break away from the magic of `DataObject` models and build types and queries from scratch. --- In this section of the documentation, we cover the fundamentals that are behind a lot of the magic that goes -into making DataObject types work. We'll create some types that are not based on DataObjects at all, and we'll +into making `DataObject` types work. We'll create some types that are not based on DataObjects at all, and we'll write some custom queries from the ground up. [info] Just because we won't be using DataObjects in this example doesn't mean you can't do it. You will lose a lot -of the benefits of the DataObject model, but this lower level API may suit your needs for really specific use +of the benefits of the `DataObject`warning model, but this lower level API may suit your needs for really specific use cases. [/info] - -[CHILDREN] - [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](https://www.silverstripe.org/blog/community-slack-channel/), @@ -23,3 +20,5 @@ and report any issues at [github.com/silverstripe/silverstripe-graphql](https:// Docs for the current stable version (3.x) can be found [here](https://github.com/silverstripe/silverstripe-graphql/tree/3) [/alert] + +[CHILDREN] diff --git a/docs/en/02_Developer_Guides/19_GraphQL/04_security_and_best_practices/index.md b/docs/en/02_Developer_Guides/19_GraphQL/04_security_and_best_practices/index.md index 39825ef9619..efef6444e70 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/04_security_and_best_practices/index.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/04_security_and_best_practices/index.md @@ -9,8 +9,6 @@ summary: A guide to keeping your GraphQL API secure and accessible In this section we'll cover several options you have for keeping your GraphQL API secure and compliant with best practices. Some of these tools require configuration, while others come pre-installed. -[CHILDREN] - [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](https://www.silverstripe.org/blog/community-slack-channel/), @@ -18,3 +16,5 @@ and report any issues at [github.com/silverstripe/silverstripe-graphql](https:// Docs for the current stable version (3.x) can be found [here](https://github.com/silverstripe/silverstripe-graphql/tree/3) [/alert] + +[CHILDREN] diff --git a/docs/en/02_Developer_Guides/19_GraphQL/05_plugins/01_overview.md b/docs/en/02_Developer_Guides/19_GraphQL/05_plugins/01_overview.md index dd83acbe85a..4ca21fb0364 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/05_plugins/01_overview.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/05_plugins/01_overview.md @@ -20,7 +20,7 @@ Docs for the current stable version (3.x) can be found 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 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... @@ -29,19 +29,19 @@ Plugins are used to distribute reusable functionality across your schema. Some e 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 interfaces, unions, and merges ancestral fields. - * An `inheritedPlugins` plugin (a bit meta!) that merges plugins from ancestral types into descendants. +* The `DataObject` model (i.e. any `DataObject` based type) has: + * An `inheritance` plugin that builds the interfaces, unions, 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`) + * 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 `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) @@ -51,24 +51,24 @@ In addition to the above, the `default` schema specifically ships with an even r All of these are defined in the `modelConfig` section of the schema (see [configuring your schema](../getting_started/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. +#### Overriding default plugins +You can override default plugins generically in the `modelConfig` section. - **app/_graphql/modelConfig.yml** - ```yaml +**app/_graphql/modelConfig.yml** +```yaml DataObject: plugins: - inheritance: false # No dataobject models get this plugin unless opted into + 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 - ``` + 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. + You can override default plugins on your specific `DataObject` type and these changes will be inherited by descendants. - **app/_graphql/models.yml** - ```yaml +**app/_graphql/models.yml** +```yaml Page: plugins: inheritance: false @@ -77,8 +77,8 @@ MyProject\MyCustomPage: {} # now has no inheritance plugin Likewise, you can do the same for operations: - **app/_graphql/models.yml** - ```yaml +**app/_graphql/models.yml** +```yaml Page: operations: read: diff --git a/docs/en/02_Developer_Guides/19_GraphQL/05_plugins/03_writing_a_complex_plugin.md b/docs/en/02_Developer_Guides/19_GraphQL/05_plugins/03_writing_a_complex_plugin.md index 6748399009e..4cd4da73ad9 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/05_plugins/03_writing_a_complex_plugin.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/05_plugins/03_writing_a_complex_plugin.md @@ -20,7 +20,7 @@ For this example, we'll imagine that a lot of our DataObjects are geocoded, and `DataExtension` that adds lat/lon information to the DataObject, and maybe allows you to ask how close it is to a given lat/lon pair. -We want any queries using these dataobjects to be able to search within a radius of a given lat/lon. +We want any queries using these DataObjects to be able to search within a radius of a given lat/lon. To do this, we'll need a few things: @@ -47,7 +47,7 @@ class GeocodableDataObject implements ModelTypePlugin { $class = $type->getModel()->getSourceClass(); - // sanity check that this is a dataobject + // sanity check that this is a DataObject Schema::invariant( is_subclass_of($class, DataObject::class), 'The %s plugin can only be applied to types generated by %s models', @@ -66,7 +66,7 @@ class GeocodableDataObject implements ModelTypePlugin } ``` -Now all dataobjects that have the extension will be forced to expose their lat/lon fields. +Now all DataObjects that have the extension will be forced to expose their lat/lon fields. ### Step 2: Add a new parameter to the queries @@ -171,8 +171,7 @@ In this case, we're going to be filtering our DataList procedurally, transformin so we need to know that things like filters and sort have already been applied, as they expect a `DataList` instance. So we'll need to do this fairly late in the chain. Afterware makes the most sense. - - ```php +```php public function apply(ModelQuery $query, Schema $schema, array $config = []): void { $class = $query->getModel()->getSourceClass(); @@ -252,7 +251,7 @@ SilverStripe\Core\Injector\Injector: ### Step 6: Apply the plugins -We can apply the plugins to queries and dataobjects one of two ways: +We can apply the plugins to queries and DataObjects one of two ways: * Add them on a case-by-case basis to our config * Add them as default plugins so that we never have to worry about it. @@ -280,7 +279,7 @@ and their `read` operations. #### Apply by default ```yaml -# apply the DataObject plugin +# apply the `DataObject` plugin SilverStripe\GraphQL\Schema\DataObject\DataObjectModel: default_plugins: geocode: true diff --git a/docs/en/02_Developer_Guides/19_GraphQL/05_plugins/index.md b/docs/en/02_Developer_Guides/19_GraphQL/05_plugins/index.md index 20de4073e0b..df2640c5ded 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/05_plugins/index.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/05_plugins/index.md @@ -11,8 +11,6 @@ and their fields and operations. Let's dive in! -[CHILDREN] - [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](https://www.silverstripe.org/blog/community-slack-channel/), @@ -20,3 +18,5 @@ and report any issues at [github.com/silverstripe/silverstripe-graphql](https:// Docs for the current stable version (3.x) can be found [here](https://github.com/silverstripe/silverstripe-graphql/tree/3) [/alert] + +[CHILDREN] diff --git a/docs/en/02_Developer_Guides/19_GraphQL/06_extending/adding_a_custom_operation.md b/docs/en/02_Developer_Guides/19_GraphQL/06_extending/adding_a_custom_operation.md index d363c95cf56..5d285216285 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/06_extending/adding_a_custom_operation.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/06_extending/adding_a_custom_operation.md @@ -71,7 +71,7 @@ public static function resolve(array $resolverContext = []): Closure ``` Now, just add the operation to the `DataObjectModel` configuration -to make it available to all DataObject types. +to make it available to all `DataObject` types. **app/_graphql/config.yml** ```yaml diff --git a/docs/en/02_Developer_Guides/19_GraphQL/06_extending/adding_middleware.md b/docs/en/02_Developer_Guides/19_GraphQL/06_extending/adding_middleware.md index 01bb22f2168..1a7eb331b2f 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/06_extending/adding_middleware.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/06_extending/adding_middleware.md @@ -26,7 +26,7 @@ but could ostensibly be used elsewhere too if the API ever accomodates such an expansion. [notice] -The middleware API in the silverstripe-graphql module is separate from other common middleware +The middleware API in the `silverstripe/graphql` module is separate from other common middleware APIs in Silverstripe CMS, such as HTTPMiddleware. [/notice] diff --git a/docs/en/02_Developer_Guides/19_GraphQL/06_extending/index.md b/docs/en/02_Developer_Guides/19_GraphQL/06_extending/index.md index 47f36991fb8..fedcacace28 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/06_extending/index.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/06_extending/index.md @@ -7,8 +7,6 @@ In this section of the documentation, we'll look at some advanced features for developers who want to extend their GraphQL server using custom models, middleware, and new operations. -[CHILDREN] - [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](https://www.silverstripe.org/blog/community-slack-channel/), @@ -16,3 +14,5 @@ and report any issues at [github.com/silverstripe/silverstripe-graphql](https:// Docs for the current stable version (3.x) can be found [here](https://github.com/silverstripe/silverstripe-graphql/tree/3) [/alert] + +[CHILDREN] diff --git a/docs/en/02_Developer_Guides/19_GraphQL/07_tips_and_tricks.md b/docs/en/02_Developer_Guides/19_GraphQL/07_tips_and_tricks.md index 57cf1a77fd5..e14e8464dc6 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/07_tips_and_tricks.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/07_tips_and_tricks.md @@ -17,7 +17,7 @@ Docs for the current stable version (3.x) can be found ## Debugging the generated code By default, the generated PHP code is put into obfuscated classnames and filenames to prevent poisoning the search -tools within IDEs. Without this, you can search for something like "Page" in your IDE and get both a generated GraphQL type (probably not what you want) and a DataObject (more likely what you want) in the results and have no easy way of differentiating between the two. +tools within IDEs. Without this, you can search for something like "Page" in your IDE and get both a generated GraphQL type (probably not what you want) and a `DataObject` (more likely what you want) in the results and have no easy way of differentiating between the two. When debugging, however, it's much easier if these classnames are human-readable. To turn on debug mode, add `DEBUG_SCHEMA=1` to your environment file and the classnames and filenames in the generated code directory will match their type names. @@ -172,7 +172,6 @@ that is fired on completion of the schema build. This file can then be consumed like Apollo. The `silverstripe/admin` module is built to consume this data and expects it to be in a web-accessible location. - ```json { "data":{ @@ -189,7 +188,7 @@ web-accessible location. } ``` -By default, the file will be stored in `public/_graphql`. Files are only generated for the `silverstripe/admin` module. +By default, the file will be stored in `public/_graphql/`. Files are only generated for the `silverstripe/admin` module. If you need these types for your own uses, add a new handler: diff --git a/docs/en/02_Developer_Guides/19_GraphQL/08_architecture_diagrams.md b/docs/en/02_Developer_Guides/19_GraphQL/08_architecture_diagrams.md index 50c352eb2b1..472d00c592d 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/08_architecture_diagrams.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/08_architecture_diagrams.md @@ -10,7 +10,7 @@ summary: A visual overview of the architecture and design of GraphQL 4 In GraphQL 3, the schema is built at request time, adding significant overhead to every API request. As the schema grows, this "Generate Schema" process becomes slower, making scalability a real problem. -In GraphQL 4, the schema is generated in a separate task, which generates code generation artefacts. These artefacts are executed at request time, meaning the schema itself imposes no penalty on the response time. +In GraphQL 4, the schema is generated during a build step, which generates code generation artefacts. These artefacts are executed at request time, meaning the schema itself imposes no penalty on the response time. [info] A useful analog to these two different approaches is a dynamic website versus a static website. In the case of the former, the PHP process is doing work on every single request. In the case of the latter, it does a lot of work once, in a separate context, in exchange for doing zero work on every page request. @@ -20,13 +20,13 @@ A useful analog to these two different approaches is a dynamic website versus a ![](../../_images/graphql/build_process.png) -* **dev/graphql/build**: This is the task that builds the schema. It also runs as a side effect of `dev/build` as a fallback. It accepts a `schema` parameter if you only want to build one schema. +* **dev/graphql/build**: This is the command that builds the schema. It also runs as a side effect of `dev/build` as a fallback. It accepts a `schema` parameter if you only want to build one schema. * **Schema Factory**: This class is responsible for rebuilding a schema or fetching an existing one (i.e. as cached generated code) * **Schema**: The most central class that governs the composition of your GraphQL schema and all of the connected services. It is largely a value object hydrated by config files and executable PHP code. -* **Plugins**: Plugins are the primary input for mutating the schema through thirdparty code. They can also be used in app code to augment core features, e.g. default resolvers for dataobjects. +* **Plugins**: Plugins are the primary input for mutating the schema through thirdparty code. They can also be used in app code to augment core features, e.g. default resolvers for DataObjects. * **Storable Schema**: A value object that is agnostic of domain-specific entities like plugins and models, and just contains the finalised set of types, queries, mutations, interfaces, unions, and scalars. It cannot be mutated once created. @@ -50,7 +50,7 @@ The concept of the "resolver stack" is illustrated later in this document. The `Schema` class is largely a value object that serves as the air traffic controller for the creation of a storable schema (i.e. generated code). Most of what it contains will be familiar to anyone with a basic understanding of GraphQL -- types, mutations, queries, etc. The magic, however, is in its nonstandard components: models and config. -Models are the layers of abstraction that create plain types and queries based on dataobjects. Imagine these few lines of config: +Models are the layers of abstraction that create plain types and queries based on DataObjects. Imagine these few lines of config: ```yaml App\Models\MyModel: @@ -60,7 +60,7 @@ App\Models\MyModel: read: true ``` -It is the model's job to interpret what `*` or "all fields" means in that context (e.g. looking at `$db`, `$has_one`, etc). It also can create a read query for that dataobject with the simple `read: true` directive, and adding something `query readMyDataObjects` to the schema for you. Models are described in more detail below. There is also a lot more to learn about the model layer in the [Working with DataObjects](../working_with_dataobjects) section. +It is the model's job to interpret what `*` or "all fields" means in that context (e.g. looking at `$db`, `$has_one`, etc). It also can create a read query for that `DataObject` with the simple `read: true` directive, and adding something `query readMyDataObjects` to the schema for you. Models are described in more detail below. There is also a lot more to learn about the model layer in the [Working with DataObjects](../working_with_DataObjects) section. The nonstandard "config" component here contains arbitrary directives, most of which influence the behaviour of models -- for instance, adding plugins, and influencing how resolvers operate. @@ -70,7 +70,7 @@ The primary role of the `Schema` class is to create a "storable schema" -- a rea ![](../../_images/graphql/models.png) -Model types are created by providing a class name to the schema. From there, it asks the `Model Creator` service to create a model for that class name. This may seem like an unnessary layer of abstraction, but in theory, models could be based on classes that are not dataobjects, and in such a case a new model creator would be required. +Model types are created by providing a class name to the schema. From there, it asks the `Model Creator` service to create a model for that class name. This may seem like an unnessary layer of abstraction, but in theory, models could be based on classes that are not DataObjects, and in such a case a new model creator would be required. The model type composes itself by interrogating the model, an implementation of `SchemaModelInterface`. This will almost always be `DataObjectModel`. The model is responsible for solving domain-specific problems pertaining to a Silverstripe project, including: @@ -91,7 +91,7 @@ The plugin layer is likely to be refactored before stablisation, but the current ![](../../_images/graphql/resolver_composition.png) -Injecting behaviour into resolvers is one of the main ways the schema can be customised. For instance, if you add a new argument to a query, the standard dataobject resolver will not know about it, so you'll want to write your own code to handle that argument. You could overwrite the entire resolver, but then you would lose key functionality from other plugins, like pagination, sort, and filtering. +Injecting behaviour into resolvers is one of the main ways the schema can be customised. For instance, if you add a new argument to a query, the standard `DataObject` resolver will not know about it, so you'll want to write your own code to handle that argument. You could overwrite the entire resolver, but then you would lose key functionality from other plugins, like pagination, sort, and filtering. To this end, resolvers are a product of composition. Each bit of functionality is just another resolver in the "stack." The stack passes the result of the previous resolver to the next resolver, while the other three parameters, `$args, $context, $info` are immutable. @@ -101,7 +101,7 @@ This pattern allows, for instance, filter plugin to run `$obj = $obj->filter(... ![](../../_images/graphql/resolver_context.png) -Sometimes, a resolver needs to be used in multiple contexts, for instance, a generic "read" resolver for a dataobject that simply runs `DataList::create($className)`. That `$className` parameter needs to come from somewhere. Normally we would use some kind of state on an instance, but because resolver methods must be static, we don't have that option. This gets really tricky. +Sometimes, a resolver needs to be used in multiple contexts, for instance, a generic "read" resolver for a `DataObject` that simply runs `DataList::create($className)`. That `$className` parameter needs to come from somewhere. Normally we would use some kind of state on an instance, but because resolver methods must be static, we don't have that option. This gets really tricky. To solve this problem, we can use "resolver context". @@ -123,10 +123,3 @@ public static function resolve(array $resolverContext) ``` As illustrated above, some resolvers in the stack can be provided context, while others may not. - - - - - - - diff --git a/docs/en/02_Developer_Guides/19_GraphQL/index.md b/docs/en/02_Developer_Guides/19_GraphQL/index.md index 49406601ff2..b96f7fd2203 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/index.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/index.md @@ -4,9 +4,7 @@ GraphQL is the content API layer for Silverstripe CMS. It is the recommended way of getting data in and out of the content management system. -For more information on GraphQL, visit its [documentation site](https://graphql.org). - -[CHILDREN includeFolders] +For more information on GraphQL in general, visit its [documentation site](https://graphql.org). [alert] You are viewing docs for a pre-release version of silverstripe/graphql (4.x). @@ -15,3 +13,5 @@ and report any issues at [github.com/silverstripe/silverstripe-graphql](https:// Docs for the current stable version (3.x) can be found [here](https://github.com/silverstripe/silverstripe-graphql/tree/3) [/alert] + +[CHILDREN includeFolders] diff --git a/docs/en/04_Changelogs/4.11.0.md b/docs/en/04_Changelogs/4.11.0.md index aa8c84ad29f..1aa4ccdcb3c 100644 --- a/docs/en/04_Changelogs/4.11.0.md +++ b/docs/en/04_Changelogs/4.11.0.md @@ -31,7 +31,7 @@ In accordance with our [PHP support policy](/Getting_Started/Server_Requirements ## GraphQL 4 major release {#graphqlv4} -Silverstripe CMS Recipe 4.11 defaults to installing `silverstripe/graphql` version 4, which has just has a stable major release. Previous releases installed version 3. +Silverstripe CMS Recipe 4.11 defaults to installing `silverstripe/graphql` version 4, which has just had a stable major release. Previous releases installed version 3. ### What does `silverstripe/graphql` do and why are you changing this? @@ -47,13 +47,16 @@ Up until CMS Recipe 4.11, Silverstripe CMS would default to using `silverstripe/ ### What do I need to know to get started? +Part of the reason why `silverstripe/graphql` v4 is so much faster than v3 is that it has a “code generation” step. Silverstripe CMS will generate PHP classes for your GraphQL schemas and stores them in a `.graphql-generated` folder in the root of your project. + If you do not have a custom schema, all you need to know is: -- There is a new `.graphql-generated` folder that your web server user will need write access to. - - By default this sits directly in your project root. If your project root is not currently writable, you will need to create the new folder yourself and set the appropriate permissions for it. +- There are two new folders that your web server user will need write access to: `.graphql-generated` and `public/_graphql`. These are now mentioned in the [Server Requirements](/getting_started/server_requirements/) documentation. + - If these folders do not exist when `silverstripe/graphql` needs them, the module will try to create them. - The GraphQL schema for the CMS will need to be generated. For the most common hosting scenarios you will be fine letting this happen during dev/build, but read the [building the schema](/developer_guides/graphql/getting_started/building_the_schema) documentation to know what your options are - especially if you have a multi-server hosting solution. +- You will need to deploy the generated schema to your test or production environment. There are several ways to do this depending on your hosting situation - see the [deploying the schema](/developer_guides/graphql/getting_started/deploying_the_schema) documentation. -If you were already using GraphQL v3 for your own custom schema and queries and want to upgrade to v4, read the [Upgrading to GraphQL 4](/upgrading/upgrading_to_graphql_4) documentation. +If you were already using GraphQL v3 for your own custom schema and queries and want to upgrade to v4, you will also need to read the [Upgrading to GraphQL 4](/upgrading/upgrading_to_graphql_4) documentation, and are encouraged to read the [GraphQL documentation](/developer_guides/graphql/) generally to make sure your existing knowledge carries over to the new major release. ### That sounds risky, do I absolutely have to use version 4? @@ -85,6 +88,12 @@ To view which dependencies require `silverstripe/graphql`, run this composer com `composer why silverstripe/graphql` +### Tracking or ignoring the `.graphql-generated` and `public/_graphql` folders + +Existing projects will not have an entry in their `.gitignore` file for `.graphql-generated` or `public/_graphql`. It is best practice for most situations to not track these folders in version control. You’ll have to manually add this entry to your `.gitignore`. + +The `.gitignore` file in `silverstripe/installer` 4.11 has been updated to ignore both of these folders. If you start a new project from `silverstripe/installer` 4.11.0 and want to track the new folders, you’ll have to update your `.gitignore` file. + ## Features and enhancements {#features-and-enhancements} ### Upload and use WebP images {#webp}