Skip to content

Commit

Permalink
Merge pull request #9835 from unclecheese/pulls/4/nuclear-refactor
Browse files Browse the repository at this point in the history
DOCS: Document GraphQL 4 BuildState changes
  • Loading branch information
chillu committed Feb 17, 2021
2 parents 15c0621 + 68eeef6 commit c6d6358
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 76 deletions.
22 changes: 21 additions & 1 deletion docs/en/00_Getting_Started/00_Server_Requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -67,3 +80,4 @@ Now we can register the middleware with our query handler:
Middlewares:
logging: '%$MyProject\Middleware\LoggingMiddleware'
```

63 changes: 20 additions & 43 deletions docs/en/02_Developer_Guides/19_GraphQL/07_tips_and_tricks.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,14 @@ 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

If you need the type name during normal execution of your app, e.g. to display in your UI, you can rely
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
Expand Down Expand Up @@ -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":{
Expand All @@ -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`.
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;
If you need these types for your own uses, add a new handler:

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.

0 comments on commit c6d6358

Please sign in to comment.