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!
|
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
|
### 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.6 | 5.3 - 7.1 | |
|
||||||
| 3.7 | 5.3 - 7.3 | [changelog](https://docs.silverstripe.org/en/3/changelogs/3.7.0/) |
|
| 3.7 | 5.3 - 7.3 | [changelog](https://docs.silverstripe.org/en/3/changelogs/3.7.0/) |
|
||||||
| 4.0 - 4.4 | 5.6+ | |
|
| 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
|
## 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.
|
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).
|
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.
|
This Composer plugin automatically tries to expose assets from your project and installed modules after installation, or after an update.
|
||||||
|
@ -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 a new resolver for a type that uses [resolver discovery](../working_with_generic_types/resolver_discovery)
|
||||||
* Adding an extension to a DataObject
|
* Adding an extension to a DataObject
|
||||||
* Adding a new subclass to a DataObject that is already exposed
|
* 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
|
### Viewing the generated code
|
||||||
|
|
||||||
By default, the generated code is placed in the `.graphql/` directory in the root of your project.
|
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
|
### Further reading
|
||||||
|
|
||||||
|
@ -114,6 +114,58 @@ don't apply in this context. Most importantly, this means you need to
|
|||||||
implement your own `canView()` checks.
|
implement your own `canView()` checks.
|
||||||
[/notice]
|
[/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,
|
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
|
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
|
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.
|
APIs in Silverstripe CMS, such as HTTPMiddleware.
|
||||||
[/notice]
|
[/notice]
|
||||||
|
|
||||||
The signature for middleware is pretty simple:
|
The signature for middleware looks like this:
|
||||||
|
|
||||||
```php
|
```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
|
* `$schema`: The underlying [Schema](http://webonyx.github.io/graphql-php/type-system/schema/) object.
|
||||||
passed to an event handler. The `$next` parameter refers to the next
|
Useful to inspect whether types are defined in a schema.
|
||||||
middleware in the chain.
|
* `$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.
|
Let's write a simple middleware that logs our queries as they come in.
|
||||||
|
|
||||||
```php
|
```php
|
||||||
|
use SilverStripe\GraphQL\QueryHandler\UserContextProvider;
|
||||||
|
use GraphQL\Type\Schema;
|
||||||
|
|
||||||
class LoggingMiddleware implements Middleware
|
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)
|
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
|
// Hand off execution to the next middleware
|
||||||
return $next($params);
|
return $next($params);
|
||||||
@ -67,3 +80,4 @@ Now we can register the middleware with our query handler:
|
|||||||
Middlewares:
|
Middlewares:
|
||||||
logging: '%$MyProject\Middleware\LoggingMiddleware'
|
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.
|
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
|
### 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.
|
on the cached typenames, which are persisted alongside your generated schema code.
|
||||||
|
|
||||||
```php
|
```php
|
||||||
Schema::create('default')->getTypeNameForClass($className);
|
SchemaBuilder::singleton()->read('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();
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Persisting queries
|
## Persisting queries
|
||||||
@ -165,15 +142,16 @@ This feature is experimental, and has not been thoroughly evaluated for security
|
|||||||
[/warning]
|
[/warning]
|
||||||
|
|
||||||
|
|
||||||
## Schema introspection
|
## Schema introspection {#schema-introspection}
|
||||||
|
|
||||||
Some GraphQL clients such as [Apollo](http://apollographql.com) require some level of 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/),
|
into the schema. The `SchemaTranscriber` class will persist this data to a static file in an event
|
||||||
this module provides a limited API for fetching it via non-graphql endpoints. By default, the `graphql/`
|
that is fired on completion of the schema build. This file can then be consumed by a client side library
|
||||||
controller provides a `types` action that will return the type schema (serialised as JSON) dynamically.
|
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":{
|
"data":{
|
||||||
"__schema":{
|
"__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 need these types for your own uses, add a new handler:
|
||||||
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`.
|
|
||||||
|
|
||||||
When `cache_types_in_filesystem` is enabled, it is recommended that you remove the extension that
|
```yml
|
||||||
provides the dynamic introspection endpoint.
|
SilverStripe\Core\Injector\Injector:
|
||||||
|
SilverStripe\EventDispatcher\Dispatch\Dispatcher:
|
||||||
```php
|
properties:
|
||||||
use SilverStripe\GraphQL\Controller;
|
handlers:
|
||||||
use SilverStripe\GraphQL\Extensions\IntrospectionProvider;
|
graphqlTranscribe:
|
||||||
|
on: [ graphqlSchemaBuild.mySchema ]
|
||||||
Controller::remove_extension(IntrospectionProvider::class);
|
handler: '%$SilverStripe\GraphQL\Schema\Services\SchemaTranscribeHandler'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This handler will only apply to events fired in the `mySchema` context.
|
||||||
|
@ -53,7 +53,8 @@ registration of types, execution of scaffolding, running queries and middleware,
|
|||||||
class has been broken up into separate concerns:
|
class has been broken up into separate concerns:
|
||||||
|
|
||||||
* `Schema` <- register your stuff here
|
* `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
|
### Upgrading
|
||||||
|
|
||||||
@ -72,7 +73,8 @@ SilverStripe\GraphQL\Manager:
|
|||||||
SilverStripe\GraphQL\Schema\Schema:
|
SilverStripe\GraphQL\Schema\Schema:
|
||||||
schemas:
|
schemas:
|
||||||
default:
|
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
|
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
|
### Upgrading
|
||||||
|
|
||||||
Move your resolvers into one or many `ResolverProvider` implementations, register them.
|
Move your resolvers into one or many classes, and register them.
|
||||||
|
|
||||||
**before**
|
**before**
|
||||||
```php
|
```php
|
||||||
@ -156,15 +158,16 @@ class LatestPostResolver implements OperationResolver
|
|||||||
```
|
```
|
||||||
|
|
||||||
**after**
|
**after**
|
||||||
|
|
||||||
|
**app/_graphql/config.yml**
|
||||||
```yaml
|
```yaml
|
||||||
SilverStripe\Core\Injector\Injector:
|
resolvers:
|
||||||
SilverStripe\GraphQL\Schema\Registry\ResolverRegistry:
|
- MyProject\Resolvers\MyResolverA
|
||||||
constructor:
|
- MyProject\Resolvers\MyResolverB
|
||||||
myResolver: '%$MyProject\Resolvers\MyResolvers'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```php
|
```php
|
||||||
class MyResolvers extends DefaultResolverProvider
|
class MyResolverA
|
||||||
{
|
{
|
||||||
public static function resolveLatestPost($object, array $args, $context, ResolveInfo $info)
|
public static function resolveLatestPost($object, array $args, $context, ResolveInfo $info)
|
||||||
{
|
{
|
||||||
@ -218,7 +221,7 @@ class MyProvider implements ScaffoldingProvider
|
|||||||
SilverStripe\GraphQL\Schema\Schema:
|
SilverStripe\GraphQL\Schema\Schema:
|
||||||
schemas:
|
schemas:
|
||||||
default:
|
default:
|
||||||
builders:
|
execute:
|
||||||
- 'MyProject\MyProvider'
|
- '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:
|
At a high-level, it needs to answer questions like:
|
||||||
|
|
||||||
* Do you have field X?
|
* Do you have field X?
|
||||||
What type is field Y?
|
What type is field Y?
|
||||||
* What are all the fields you offer?
|
* What are all the fields you offer?
|
||||||
* What operations do you provide?
|
* What operations do you provide?
|
||||||
* Do you require any extra types to be added to the schema?
|
* Do you require any extra types to be added to the schema?
|
||||||
@ -304,14 +307,33 @@ Change the casing in your queries.
|
|||||||
**before**
|
**before**
|
||||||
```graphql
|
```graphql
|
||||||
query readPages {
|
query readPages {
|
||||||
nodes {
|
edges {
|
||||||
Title
|
nodes {
|
||||||
ShowInMenus
|
Title
|
||||||
|
ShowInMenus
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**after**
|
**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
|
```graphql
|
||||||
query readPages {
|
query readPages {
|
||||||
nodes {
|
nodes {
|
||||||
@ -321,6 +343,7 @@ query readPages {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## DataObject type names are simpler
|
## DataObject type names are simpler
|
||||||
|
|
||||||
To avoid naming collisions, the 3.x release of the module used a pretty aggressive approach to ensuring
|
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
|
CANCELLED: Cancelled
|
||||||
PENDING: Pending
|
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