diff --git a/docs/en/00_Getting_Started/00_Server_Requirements.md b/docs/en/00_Getting_Started/00_Server_Requirements.md index ee2ca91ac..6c51376ca 100644 --- a/docs/en/00_Getting_Started/00_Server_Requirements.md +++ b/docs/en/00_Getting_Started/00_Server_Requirements.md @@ -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 diff --git a/docs/en/02_Developer_Guides/01_Templates/03_Requirements.md b/docs/en/02_Developer_Guides/01_Templates/03_Requirements.md index ee7bb4298..965f521ac 100644 --- a/docs/en/02_Developer_Guides/01_Templates/03_Requirements.md +++ b/docs/en/02_Developer_Guides/01_Templates/03_Requirements.md @@ -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. 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 96a57d0ce..acd11ef68 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 @@ -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 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 c629c7a17..382fbe5e7 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 @@ -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 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 08275c043..44afb3fe5 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 @@ -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 : ''; + )); // 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' ``` + 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 2f3d249cd..29dcd71ef 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,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. diff --git a/docs/en/02_Developer_Guides/19_GraphQL/08_upgrading.md b/docs/en/02_Developer_Guides/19_GraphQL/08_upgrading.md index cf884dc96..2bdd520c3 100644 --- a/docs/en/02_Developer_Guides/19_GraphQL/08_upgrading.md +++ b/docs/en/02_Developer_Guides/19_GraphQL/08_upgrading.md @@ -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.