mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge pull request #9835 from unclecheese/pulls/4/nuclear-refactor
DOCS: Document GraphQL 4 BuildState changes
This commit is contained in:
commit
c6d6358e45
@ -106,6 +106,26 @@ app/
|
||||
|
||||
Don't forget to include this additional folder in any syncing and backup processes!
|
||||
|
||||
### Building, Packaging and Deployment
|
||||
|
||||
It is common to build a SilverStripe application into a package on one environment (e.g. a CI server),
|
||||
and then deploy the package to a (separate) webserver environment(s).
|
||||
This approach relies on all auto-generated files required by SilverStripe to
|
||||
be included in the package, or generated on the fly on each webserver environment.
|
||||
|
||||
The easiest way to ensure this is to commit auto generated files to source control.
|
||||
If those changes are considered too noisy, here's some pointers for auto-generated files
|
||||
to trigger and include in a deployment package:
|
||||
|
||||
* `public/_resources/`: Frontend assets copied from the (inaccessible) `vendor/` folder
|
||||
via [silverstripe/vendor-plugin](https://github.com/silverstripe/vendor-plugin).
|
||||
See [Templates: Requirements](/developer_guides/templates/requirements#exposing-assets-webroot).
|
||||
* `.graphql/` and `public/_graphql/`: Schema and type definitions required by CMS and any GraphQL API endpoint. Generated through
|
||||
[silverstripe/graphql v4](https://github.com/silverstripe/silverstripe-graphql).
|
||||
Triggered by `dev/build`, or [GraphQL Schema Build](/developer_guides/graphql/getting_started/building_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).
|
||||
|
||||
### Web Worker Concurrency
|
||||
|
||||
@ -215,7 +235,7 @@ SilverStripe's PHP support has changed over time and if you are looking to upgra
|
||||
| 3.6 | 5.3 - 7.1 | |
|
||||
| 3.7 | 5.3 - 7.3 | [changelog](https://docs.silverstripe.org/en/3/changelogs/3.7.0/) |
|
||||
| 4.0 - 4.4 | 5.6+ | |
|
||||
| 4.5+ (unreleased) | 7.1+ | [blog post](https://www.silverstripe.org/blog/our-plan-for-ending-php-5-6-support-in-silverstripe-4/) |
|
||||
| 4.5+ | 7.1+ | [blog post](https://www.silverstripe.org/blog/our-plan-for-ending-php-5-6-support-in-silverstripe-4/) |
|
||||
|
||||
|
||||
## CMS browser requirements
|
||||
|
@ -47,7 +47,7 @@ Files contained inside the `app/client/dist` and `app/images` will be made publi
|
||||
|
||||
SilverStripe projects should not track the "resources" directory in their source control system.
|
||||
|
||||
### Exposing assets in the web root
|
||||
### Exposing assets in the web root {#exposing-assets-webroot}
|
||||
|
||||
SilverStripe projects ship with [silverstripe/vendor-plugin](https://github.com/silverstripe/vendor-plugin).
|
||||
This Composer plugin automatically tries to expose assets from your project and installed modules after installation, or after an update.
|
||||
|
@ -60,7 +60,7 @@ for initial builds and deployments, but during incremental development this can
|
||||
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 re-render. This reduces build times to **under one second** for incremental changes.
|
||||
|
||||
#### Clearing the cache
|
||||
|
||||
@ -81,10 +81,23 @@ tangential changes such as:
|
||||
* 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.
|
||||
|
||||
|
||||
|
||||
### Viewing the generated code
|
||||
|
||||
By default, the generated code is placed in the `.graphql/` 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.
|
||||
|
||||
Additional files are generated for CMS operation in `public/_graphql/`, and
|
||||
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.
|
||||
|
||||
|
||||
### Further reading
|
||||
|
||||
|
@ -114,6 +114,58 @@ don't apply in this context. Most importantly, this means you need to
|
||||
implement your own `canView()` checks.
|
||||
[/notice]
|
||||
|
||||
## Resolver Method Arguments
|
||||
|
||||
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.
|
||||
|
||||
## Using Context Providers
|
||||
|
||||
The `$context` array can be useful to get access to the HTTP request,
|
||||
retrieve the current member, or find out details about the schema.
|
||||
You can use it through implementors of the `SilverStripe\GraphQL\Schema\Interfaces\ContextProvider` interface.
|
||||
In the example below, we'll demonstrate how you could limit viewing the country code to
|
||||
users with ADMIN permissions.
|
||||
|
||||
|
||||
**app/src/Resolvers/MyResolver.php**
|
||||
```php
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
use SilverStripe\GraphQL\QueryHandler\UserContextProvider;
|
||||
use SilverStripe\Security\Permission;
|
||||
|
||||
class MyResolver
|
||||
{
|
||||
public static function resolveCountries($value = null, array $args = [], array $context = [], ?ResolveInfo $info = null): array
|
||||
{
|
||||
$member = UserContextProvider::get($context);
|
||||
$canViewCode = ($member && Permission::checkMember($member, 'ADMIN'));
|
||||
$results = [];
|
||||
$countries = Injector::inst()->get(Locales::class)->getCountries();
|
||||
foreach ($countries as $code => $name) {
|
||||
$results[] = [
|
||||
'code' => $canViewCode ? $code : '',
|
||||
'name' => $name
|
||||
];
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Resolver Discovery
|
||||
|
||||
This is great, but as we write more and more queries for types with more and more fields,
|
||||
it's going to get awfully laborious mapping all these resolvers. Let's clean this up a bit by
|
||||
adding a bit of convention over configuration, and save ourselves a lot of time to boot. We can do
|
||||
|
@ -30,26 +30,39 @@ The middleware API in the silverstripe-graphql module is separate from other com
|
||||
APIs in Silverstripe CMS, such as HTTPMiddleware.
|
||||
[/notice]
|
||||
|
||||
The signature for middleware is pretty simple:
|
||||
The signature for middleware looks like this:
|
||||
|
||||
```php
|
||||
public function process(array $params, callable $next)
|
||||
public function process(Schema $schema, $query, $context, $vars, callable $next)
|
||||
```
|
||||
|
||||
`$params` is an arbitrary array of data, much like an event object
|
||||
passed to an event handler. The `$next` parameter refers to the next
|
||||
middleware in the chain.
|
||||
* `$schema`: The underlying [Schema](http://webonyx.github.io/graphql-php/type-system/schema/) object.
|
||||
Useful to inspect whether types are defined in a schema.
|
||||
* `$query`: The raw query string.
|
||||
* `$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.
|
||||
* `$vars`: An array of (optional) [Query Variables](https://graphql.org/learn/queries/#variables).
|
||||
* `$next`: A callable referring to the next middleware in the chain
|
||||
|
||||
Let's write a simple middleware that logs our queries as they come in.
|
||||
|
||||
```php
|
||||
use SilverStripe\GraphQL\QueryHandler\UserContextProvider;
|
||||
use GraphQL\Type\Schema;
|
||||
|
||||
class LoggingMiddleware implements Middleware
|
||||
{
|
||||
public function process(array $params, callable $next)
|
||||
public function process(Schema $schema, $query, $context, $vars, callable $next)
|
||||
{
|
||||
$query = $params['query'];
|
||||
$member = UserContextProvider::get($context);
|
||||
|
||||
Injector::inst()->get(LoggerInterface::class)
|
||||
->info('Query executed: ' . $query);
|
||||
->info(sprintf(
|
||||
'Query executed: %s by %s',
|
||||
$query,
|
||||
$member ? $member->Title : '<anonymous>';
|
||||
));
|
||||
|
||||
// Hand off execution to the next middleware
|
||||
return $next($params);
|
||||
@ -67,3 +80,4 @@ Now we can register the middleware with our query handler:
|
||||
Middlewares:
|
||||
logging: '%$MyProject\Middleware\LoggingMiddleware'
|
||||
```
|
||||
|
||||
|
@ -17,16 +17,6 @@ Docs for the current stable version (3.x) can be found
|
||||
|
||||
Often times, you'll need to know the name of the type given a class name. There's a bit of context to this.
|
||||
|
||||
### Getting the type name at build time
|
||||
|
||||
If you need to know the name of the type _during the build_, e.g. creating the name of an operation, field, query, etc,
|
||||
you should use the `Build::requireActiveBuild()` accessor. This will get you the schema that is currently being built,
|
||||
and throw if no build is active. A more tolerant method is `getActiveBuild()` which will return null if no schema
|
||||
is being built.
|
||||
|
||||
```php
|
||||
Build::requireActiveBuild()->findOrMakeModel($className)->getName();
|
||||
```
|
||||
|
||||
### Getting the type name from within your app
|
||||
|
||||
@ -34,20 +24,7 @@ If you need the type name during normal execution of your app, e.g. to display i
|
||||
on the cached typenames, which are persisted alongside your generated schema code.
|
||||
|
||||
```php
|
||||
Schema::create('default')->getTypeNameForClass($className);
|
||||
```
|
||||
|
||||
### Why is there a difference?
|
||||
|
||||
It is expensive to load all of the schema config. The `getTypeNameForClass` function avoids the need to
|
||||
load the config, and reads directly from the cache. To be clear, the following is functionally equivalent,
|
||||
but slow:
|
||||
|
||||
```php
|
||||
Schema::create('default')
|
||||
->loadFromConfig()
|
||||
->findOrMakeModel($className)
|
||||
->getName();
|
||||
SchemaBuilder::singleton()->read('default')->getTypeNameForClass($className);
|
||||
```
|
||||
|
||||
## Persisting queries
|
||||
@ -165,15 +142,16 @@ This feature is experimental, and has not been thoroughly evaluated for security
|
||||
[/warning]
|
||||
|
||||
|
||||
## Schema introspection
|
||||
## Schema introspection {#schema-introspection}
|
||||
|
||||
Some GraphQL clients such as [Apollo](http://apollographql.com) require some level of introspection
|
||||
into the schema. While introspection is [part of the GraphQL spec](http://graphql.org/learn/introspection/),
|
||||
this module provides a limited API for fetching it via non-graphql endpoints. By default, the `graphql/`
|
||||
controller provides a `types` action that will return the type schema (serialised as JSON) dynamically.
|
||||
into the schema. The `SchemaTranscriber` class will persist this data to a static file in an event
|
||||
that is fired on completion of the schema build. This file can then be consumed by a client side library
|
||||
like Apollo. The `silverstripe/admin` module is built to consume this data and expects it to be in a
|
||||
web-accessible location.
|
||||
|
||||
*GET http://example.com/graphql/types*
|
||||
```js
|
||||
|
||||
```json
|
||||
{
|
||||
"data":{
|
||||
"__schema":{
|
||||
@ -189,19 +167,18 @@ controller provides a `types` action that will return the type schema (serialise
|
||||
}
|
||||
|
||||
```
|
||||
By default, the file will be stored in `public/_graphql`. Files are only generated for the `silverstripe/admin` module.
|
||||
|
||||
As your schema grows, introspecting it dynamically may have a performance hit. Alternatively,
|
||||
if you have the `silverstripe/assets` module installed (as it is in the default SilverStripe installation),
|
||||
GraphQL can cache your schema as a flat file in the `assets/` directory. To enable this, simply
|
||||
set the `cache_types_in_filesystem` setting to `true` on `SilverStripe\GraphQL\Controller`. Once enabled,
|
||||
a `types.graphql` file will be written to your `assets/` directory on `flush`.
|
||||
If you need these types for your own uses, add a new handler:
|
||||
|
||||
When `cache_types_in_filesystem` is enabled, it is recommended that you remove the extension that
|
||||
provides the dynamic introspection endpoint.
|
||||
|
||||
```php
|
||||
use SilverStripe\GraphQL\Controller;
|
||||
use SilverStripe\GraphQL\Extensions\IntrospectionProvider;
|
||||
|
||||
Controller::remove_extension(IntrospectionProvider::class);
|
||||
```yml
|
||||
SilverStripe\Core\Injector\Injector:
|
||||
SilverStripe\EventDispatcher\Dispatch\Dispatcher:
|
||||
properties:
|
||||
handlers:
|
||||
graphqlTranscribe:
|
||||
on: [ graphqlSchemaBuild.mySchema ]
|
||||
handler: '%$SilverStripe\GraphQL\Schema\Services\SchemaTranscribeHandler'
|
||||
```
|
||||
|
||||
This handler will only apply to events fired in the `mySchema` context.
|
||||
|
@ -8,7 +8,7 @@ summary: A high-level view of what you'll need to change when upgrading to Graph
|
||||
[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]
|
||||
@ -42,7 +42,7 @@ Most of the time, the name of your schema is `default`. If you're editing DataOb
|
||||
with GraphQL in the CMS, you may have to build the `admin` schema as well.
|
||||
[/info]
|
||||
|
||||
This build process is a larger topic with a few more things to be aware of.
|
||||
This build process is a larger topic with a few more things to be aware of.
|
||||
Check the [building the schema](getting_started/building_the_schema) documentation to learn more.
|
||||
|
||||
|
||||
@ -53,7 +53,8 @@ registration of types, execution of scaffolding, running queries and middleware,
|
||||
class has been broken up into separate concerns:
|
||||
|
||||
* `Schema` <- register your stuff here
|
||||
* `QueryHandlerInterface` <- Handles GraphQL queries. You'll probably never have to touch it.
|
||||
* `QueryHandlerInterface` <- Handles GraphQL queries, applies middlewares and context.
|
||||
You'll probably never have to touch it.
|
||||
|
||||
### Upgrading
|
||||
|
||||
@ -72,7 +73,8 @@ SilverStripe\GraphQL\Manager:
|
||||
SilverStripe\GraphQL\Schema\Schema:
|
||||
schemas:
|
||||
default:
|
||||
src: app/_graphql # A directory of your choice
|
||||
src:
|
||||
- app/_graphql # A directory of your choice
|
||||
```
|
||||
|
||||
Add the appropriate yaml files to the directory. For more information on this pattern, see
|
||||
@ -142,7 +144,7 @@ and moved into a class.
|
||||
|
||||
### Upgrading
|
||||
|
||||
Move your resolvers into one or many `ResolverProvider` implementations, register them.
|
||||
Move your resolvers into one or many classes, and register them.
|
||||
|
||||
**before**
|
||||
```php
|
||||
@ -156,15 +158,16 @@ class LatestPostResolver implements OperationResolver
|
||||
```
|
||||
|
||||
**after**
|
||||
|
||||
**app/_graphql/config.yml**
|
||||
```yaml
|
||||
SilverStripe\Core\Injector\Injector:
|
||||
SilverStripe\GraphQL\Schema\Registry\ResolverRegistry:
|
||||
constructor:
|
||||
myResolver: '%$MyProject\Resolvers\MyResolvers'
|
||||
resolvers:
|
||||
- MyProject\Resolvers\MyResolverA
|
||||
- MyProject\Resolvers\MyResolverB
|
||||
```
|
||||
|
||||
```php
|
||||
class MyResolvers extends DefaultResolverProvider
|
||||
class MyResolverA
|
||||
{
|
||||
public static function resolveLatestPost($object, array $args, $context, ResolveInfo $info)
|
||||
{
|
||||
@ -218,7 +221,7 @@ class MyProvider implements ScaffoldingProvider
|
||||
SilverStripe\GraphQL\Schema\Schema:
|
||||
schemas:
|
||||
default:
|
||||
builders:
|
||||
execute:
|
||||
- 'MyProject\MyProvider'
|
||||
```
|
||||
|
||||
@ -246,7 +249,7 @@ A model type is just a type that is backed by a class that express awareness of
|
||||
At a high-level, it needs to answer questions like:
|
||||
|
||||
* Do you have field X?
|
||||
What type is field Y?
|
||||
What type is field Y?
|
||||
* What are all the fields you offer?
|
||||
* What operations do you provide?
|
||||
* Do you require any extra types to be added to the schema?
|
||||
@ -304,14 +307,33 @@ Change the casing in your queries.
|
||||
**before**
|
||||
```graphql
|
||||
query readPages {
|
||||
nodes {
|
||||
Title
|
||||
ShowInMenus
|
||||
edges {
|
||||
nodes {
|
||||
Title
|
||||
ShowInMenus
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**after**
|
||||
```graphql
|
||||
query readPages {
|
||||
edges {
|
||||
node {
|
||||
title
|
||||
showInMenus
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `edges` no longer required
|
||||
|
||||
We don't have [cursor-based pagination](https://graphql.org/learn/pagination/) in Silverstripe CMS, so
|
||||
the use of `edges` is merely for convention. You can eliminate a layer here and just use `nodes`, but `edges`
|
||||
still exists for backward compatibility.
|
||||
|
||||
```graphql
|
||||
query readPages {
|
||||
nodes {
|
||||
@ -321,6 +343,7 @@ query readPages {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## DataObject type names are simpler
|
||||
|
||||
To avoid naming collisions, the 3.x release of the module used a pretty aggressive approach to ensuring
|
||||
@ -398,10 +421,3 @@ Status:
|
||||
CANCELLED: Cancelled
|
||||
PENDING: Pending
|
||||
```
|
||||
|
||||
## Middleware signature is more loosely typed
|
||||
|
||||
In the 3.x release, `QueryMiddleware` was a very specific implementation that took parameters that were unique
|
||||
to queries. The middleware pattern is now more generic and accepts a loosely-typed `params` array that can consist
|
||||
of anything -- more like an `event` parameter for an event handler. If you've defined custom middleware, you'll
|
||||
need to update it. Check out the [adding middleware](extending/adding_middleware) section for more information.
|
||||
|
Loading…
Reference in New Issue
Block a user