Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Execution middleware #762

Merged
merged 53 commits into from
May 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
471dacd
execution middleware
May 3, 2021
daa8bc5
fix styles
May 3, 2021
a4dcaf4
unused variables
May 4, 2021
30747b7
unused variables
May 4, 2021
ccfe692
fix style
May 4, 2021
faad9af
Use the app instance we already have
mfn May 4, 2021
6743cf2
Fix "Required parameter $next follows optional parameter $opts"
mfn May 4, 2021
a6413fb
Merge branch 'master' into execution_middleware
mfn May 4, 2021
48473b1
Fix phpdoc, thanks to phpstan
mfn May 4, 2021
ede66f8
Derive executionMiddleware from current schema, if available
mfn May 4, 2021
a48eb9c
Change visibility of executionMiddleware to protected
mfn May 4, 2021
10f6a4d
Remove unused class
mfn May 4, 2021
020fe09
Introduce own OperationParams which additionally can handle parsing t…
mfn May 4, 2021
6b975f7
Refine signature of the execution middleware
mfn May 4, 2021
306ff80
Rewrite the execution layer to primarily work with the execution midd…
mfn May 4, 2021
14a8af1
Add AddAuthUserContextValueMiddleware
mfn May 4, 2021
bf37460
Add AutomaticPersistedQueriesMiddleware
mfn May 4, 2021
81e0f2e
Add ValidateOperationParamsMiddleware
mfn May 4, 2021
7230a5f
Add GraphqlExecutionMiddleware
mfn May 4, 2021
ed3c067
Define the default set of execution middlewares
mfn May 4, 2021
ebed06b
Adapt tests
mfn May 4, 2021
5ec4e78
Update phpstan baseline
mfn May 4, 2021
773678f
Re-introduce the previous versions of query and queryAndReturnResult
mfn May 6, 2021
d3b1d76
Merge branch 'master' into execution_middleware
mfn May 6, 2021
d73619d
Fix tests to use the httpGraphql helper which runs the middlewares
mfn May 6, 2021
349198e
Fix return value of query method
mfn May 6, 2021
a99c7e2
Update phpstan-baseline
mfn May 6, 2021
d486c6b
Rename methods (once more)
mfn May 8, 2021
dd214e8
De-promote method from being public
mfn May 8, 2021
0c98a04
Document the new method on the Facade for IDE support
mfn May 8, 2021
1c08e20
Merge branch 'master' into execution_middleware
mfn May 8, 2021
bd807c6
Inject graphql instead of explicitly resolving via container
mfn May 8, 2021
a5d420c
Split execution, middleware resolving and decorating into distinct steps
mfn May 8, 2021
2cff8e5
Merge branch 'master' into execution_middleware
mfn May 8, 2021
05de59a
Adapt middleware to already receive the $schema
mfn May 8, 2021
eb10d5f
Refactor queryAndReturnResult method to use new execution / middlewar…
mfn May 8, 2021
01fd514
Remove fromBaseOperationParams and just bake it into the constructor
mfn May 8, 2021
5a91424
Merge branch 'master' into execution_middleware
mfn May 8, 2021
ff5bf93
Be explicit about the returned value and improve phpdoc
mfn May 9, 2021
367cc0c
Add overview about the different middleware concepts available
mfn May 9, 2021
d72fb24
Add changelog entry
mfn May 9, 2021
ad784d1
Merge branch 'master' into execution_middleware
mfn May 9, 2021
ffa50d5
Clarify the behaviour of AutomaticPersistedQueriesMiddleware
mfn May 10, 2021
588cb31
changelog: adapt and clean up the breaking changes section
mfn May 10, 2021
34f2ce3
Fix bug with not properly getting the per-schema execution middleware
mfn May 10, 2021
f769fd0
Fix order of test arguments and just use the whole payload
mfn May 10, 2021
ed2d937
The schemaName doesn't need to be nullable
mfn May 10, 2021
315619b
Test precedence of per-schema middelware
mfn May 10, 2021
0594d42
Merge branch 'master' into execution_middleware
mfn May 10, 2021
d8d09bd
Fix typo in changelog
mfn May 10, 2021
3b3a68e
Remove extra white space
mfn May 10, 2021
16b2c42
Update how to enable the unused variables check
mfn May 10, 2021
d1a748f
Clarify use of middleware vs. execution_middleware in the config
mfn May 10, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 20 additions & 29 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ CHANGELOG
--------------

## Breaking changes
- As part of moving the architecture to an execution based middleware approach,
the following methods have been removed:
- `\Rebing\GraphQL\GraphQLController::handleAutomaticPersistQueries` has been
replaced by the `AutomaticPersistedQueriesMiddleware` middleware
- `\Rebing\GraphQL\GraphQLController::queryContext` has been
replaced by the `AddAuthUserContextValueMiddleware` middleware\
If you relied on overriding `queryContext` to inject a custom context, you
now need to create your own execution middleware and add to your
configuration
- `\Rebing\GraphQL\GraphQLController::executeQuery` has become obsolete, no
direct replacement.

- Routing has been rewritten and simplified [\#757 / mfn](https://github.com/rebing/graphql-laravel/pull/757)
- All routing related configuration is now within the top level `route`
configuration key
Expand Down Expand Up @@ -59,42 +71,21 @@ CHANGELOG
- Drop support for configuration the name of the variable for the variables (`params_key`)
- `GraphQLUploadMiddleware` has been removed (`RequestParser` includes this functionality)
- Empty GraphQL queries now return a proper validated GraphQL error
- Signature changes In `\Rebing\GraphQL\GraphQLController`:
- old: `protected function executeQuery(string $schema, array $input): array`
new: `protected function executeQuery(string $schema, OperationParams $params): array`
- old: `protected function queryContext(string $query, ?array $params, string $schema)`
new: `protected function queryContext(string $query, ?array $variables, string $schema)`
- old: `protected function handleAutomaticPersistQueries(string $schemaName, array $input): string`
new: `protected function handleAutomaticPersistQueries(string $schemaName, OperationParams $operation): string`

- In `\Rebing\GraphQL\GraphQLController`, renamed all occurrences of `$schema` to `$schemaName`
This is to reduce the confusion as the code in some other places uses `$schema`
for the actual schema itself (either as an object or array form).
This changes the signature on the following methods:
- old: `protected function executeQuery(string $schema, OperationParams $params): array`
new: `protected function executeQuery(string $schemaName, OperationParams $params): array`
- old: `protected function queryContext(string $query, ?array $variables, string $schema)`
new: `protected function queryContext(string $query, ?array $variables, string $schemaName)`

- In `\Rebing\GraphQL\GraphQL`, renamed remaining instances of `$params` to `$variables`
After switching to `RequestParser`, the support for changing the variable name
what was supposed to `params_key` has gone and thus the name isn't fitting anymore
what was supposed to `params_key` has gone and thus the name isn't fitting anymore.
Also, the default value for `$variables` has been changed to `null` to better
fit the how `OperationParams` works:
- old: `public function query(string $query, ?array $params = [], array $opts = []): array`
new: `public function query(string $query, ?array $variables = [], array $opts = []): array`
new: `public function query(string $query, ?array $variables = null, array $opts = []): array`
- old: `public function queryAndReturnResult(string $query, ?array $params = [], array $opts = []): ExecutionResult`
new: `public function queryAndReturnResult(string $query, ?array $variables = [], array $opts = []): ExecutionResult`

- As part of APQ parsed query support [\#740 / mfn](https://github.com/rebing/graphql-laravel/pull/740):
- In `\Rebing\GraphQL\GraphQLController`, the following signature changed:
- old: `protected function handleAutomaticPersistQueries(string $schemaName, OperationParams $operation): string`
new: `protected function handleAutomaticPersistQueries(string $schemaName, OperationParams $operation): array`
- In `\Rebing\GraphQL\GraphQL`, the following signature changed:
- old: `public function query(string $query, ?array $variables = [], array $opts = []): array`
new: `public function query($query, ?array $variables = [], array $opts = []): array`
- old: `public function queryAndReturnResult(string $query, ?array $variables = [], array $opts = []): ExecutionResult`
new: `public function queryAndReturnResult($query, ?array $variables = [], array $opts = []): ExecutionResult`
new: `public function queryAndReturnResult(string $query, ?array $variables = null, array $opts = []): ExecutionResult`

### Added
- The primary execution of the GraphQL request is now piped through middlewares [\#762 / crissi and mfn](https://github.com/rebing/graphql-laravel/pull/762)\
This allows greater flexibility for enabling/disabling certain functionality
as well as bringing in new features without having to open up the library.
- Primarily register \Rebing\GraphQL\GraphQL as service and keep `'graphql'` as alias [\#768 / mfn](https://github.com/rebing/graphql-laravel/pull/768)
- Automatic Persisted Queries (APQ) now cache the parsed query [\#740 / mfn](https://github.com/rebing/graphql-laravel/pull/740)\
This avoids having to re-parse the same queries over and over again.
Expand Down
63 changes: 58 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ Use Facebook's GraphQL with Laravel 6.0+. It is based on the [PHP port of GraphQ
* Supports multiple schemas
* per schema queries/mutations/types
* per schema HTTP middlewares
* Custom GraphQL **resolver middleware** (_not_ HTTP middleware) can be defined for each query/mutation
* per schema GraphQL execution middlewares
* Custom GraphQL **resolver middleware** can be defined for each query/mutation

When using the `SelectFields` class for Eloquent support, additional features are available:
* Queries return **types**, which can have custom **privacy** settings.
Expand Down Expand Up @@ -69,6 +70,7 @@ The default GraphiQL view makes use of the global `csrf_token()` helper function
- [A word on declaring a field `nonNull`](#a-word-on-declaring-a-field-nonnull)
- [Data loading](#data-loading)
- [GraphiQL](#graphiql)
- [Middleware Overview](#middleware-overview)
- [Schemas](#schemas)
- [Creating a query](#creating-a-query)
- [Creating a mutation](#creating-a-mutation)
Expand Down Expand Up @@ -202,10 +204,58 @@ route.

If you are using multiple schemas, you can access them via `/graphiql/<schema name>`.

### Middleware Overview

The following middleware concepts are supported:

- HTTP middleware (i.e. from Laravel)
- GraphQL execution middleware
- GraphQL resolver middleware

Briefly said, a middleware _usually_ is a class:
- with a `handle` method
- receiving a fixed set of parameters plus a callable for the next middleware
- is responsible for calling the "next" middleware\
Usually a middleware does just that but may decide to not do that and
just return
- has the freedom to mutate the parameters passed on

#### HTTP middleware

Any [Laravel compatible HTTP middleware](https://laravel.com/docs/middleware)
can be provided on a global level for all GraphQL endpoints via the config
`graphql.route.middleware` or on a per-schema basis via
`graphql.schemas.<yourschema>.middleware`. The per-schema middleware overrides
the global one.

#### GraphQL execution middleware

The processing of a GraphQL request, henceforth called "execution", flows
through a set of middelwares.

They can be set on global level via `config.execution_middleware` or per-schema
via `config.schemas.<yourschema>.execution_middleware`.

By default, the recommended set of middlewares is provided on the global level.

Note: the execution of the GraphQL request _itself_ is also implemented via a
middleware, which is usually expected to be called last (and does not call
further middlewares). In case you're interested in the details, please see
`\Rebing\GraphQL\GraphQL::appendGraphqlExecutionMiddleware`

#### GraphQL resolver middleware

After the HTTP middleware and the execution middelware is applied, the
"resolver middleware" is executed for the query/mutation being targeted
**before** the actual `resolve()` method is called.

See [Resolver middleware](#resolver-middleware) for more details.

### Schemas

Schemas are required for defining GraphQL endpoints. You can define multiple schemas and assign different **HTTP middleware** to them,
in addition to the global middleware. For example:
Schemas are required for defining GraphQL endpoints. You can define multiple
schemas and assign different **HTTP middleware** and **execution middleware** to
them, in addition to the global middleware. For example:

```php
'schema' => 'default',
Expand Down Expand Up @@ -239,6 +289,9 @@ in addition to the global middleware. For example:

],
'middleware' => ['auth'],
'execution_middleware' => [
\Rebing\GraphQL\Support\ExecutionMiddleware\UnusedVariablesMiddleware::class,
],
],
],
```
Expand Down Expand Up @@ -2591,8 +2644,8 @@ Variables provided:

In this case, nothing happens and `optional_id` will be treated as not being provided.

To prevent such scenarios, you can enable the config option `detect_unused_variables`
and set it to `true`.
To prevent such scenarios, you can add the `UnusedVariablesMiddleware` to your
`execution_middleware`.

## Configuration options

Expand Down
21 changes: 19 additions & 2 deletions config/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,12 @@
'types' => [
// ExampleType::class,
],

// Laravel HTTP middleware
'middleware' => [],

// An array of middlewares, overrides the global ones
'execution_middleware' => null,
],
],

Expand Down Expand Up @@ -190,6 +195,12 @@
/*
* Automatic Persisted Queries (APQ)
* See https://www.apollographql.com/docs/apollo-server/performance/apq/
*
* Note 1: this requires the `AutomaticPersistedQueriesMiddleware` being enabled
*
* Note 2: even if APQ is disabled per configuration and, according to the "APQ specs" (see above),
* to return a correct response in case it's not enabled, the middleware needs to be active.
* Of course if you know you do not have a need for APQ, feel free to remove the middleware completely.
*/
'apq' => [
// Enable/Disable APQ - See https://www.apollographql.com/docs/apollo-server/performance/apq/#disabling-apq
Expand All @@ -206,7 +217,13 @@
],

/*
* If enabled, variables provided but not consumed by the query will throw an error
* Execution middlewares
*/
'detect_unused_variables' => false,
'execution_middleware' => [
\Rebing\GraphQL\Support\ExecutionMiddleware\ValidateOperationParamsMiddleware::class,
// AutomaticPersistedQueriesMiddleware listed even if APQ is disabled, see the docs for the `'apq'` configuration
\Rebing\GraphQL\Support\ExecutionMiddleware\AutomaticPersistedQueriesMiddleware::class,
\Rebing\GraphQL\Support\ExecutionMiddleware\AddAuthUserContextValueMiddleware::class,
// \Rebing\GraphQL\Support\ExecutionMiddleware\UnusedVariablesMiddleware::class,
],
];
50 changes: 10 additions & 40 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -55,31 +55,11 @@ parameters:
count: 1
path: src/GraphQL.php

-
message: "#^Method Rebing\\\\GraphQL\\\\GraphQL\\:\\:query\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: src/GraphQL.php

-
message: "#^Method Rebing\\\\GraphQL\\\\GraphQL\\:\\:schema\\(\\) has parameter \\$schema with no value type specified in iterable type array\\.$#"
count: 1
path: src/GraphQL.php

-
message: "#^Method Rebing\\\\GraphQL\\\\GraphQLController\\:\\:executeQuery\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: src/GraphQLController.php

-
message: "#^Method Rebing\\\\GraphQL\\\\GraphQLController\\:\\:queryContext\\(\\) has no return typehint specified\\.$#"
count: 1
path: src/GraphQLController.php

-
message: "#^Method Rebing\\\\GraphQL\\\\GraphQLController\\:\\:queryContext\\(\\) has parameter \\$variables with no value type specified in iterable type array\\.$#"
count: 1
path: src/GraphQLController.php

-
message: "#^Access to an undefined property GraphQL\\\\Type\\\\Definition\\\\InputObjectField\\|stdClass\\:\\:\\$alias\\.$#"
count: 1
Expand Down Expand Up @@ -140,6 +120,11 @@ parameters:
count: 1
path: src/Support/AliasArguments/ArrayKeyChange.php

-
message: "#^Parameter \\#2 \\$schema of method Rebing\\\\GraphQL\\\\Support\\\\ExecutionMiddleware\\\\AbstractExecutionMiddleware\\:\\:handle\\(\\) expects GraphQL\\\\Type\\\\Schema, Closure\\(\\.\\.\\.mixed\\)\\: mixed given\\.$#"
count: 1
path: src/Support/ExecutionMiddleware/AbstractExecutionMiddleware.php

-
message: "#^Cannot call method getName\\(\\) on ReflectionType\\|null\\.$#"
count: 1
Expand Down Expand Up @@ -490,16 +475,6 @@ parameters:
count: 1
path: tests/Database/AuthorizeArgsTests/GraphQLContext.php

-
message: "#^Method Rebing\\\\GraphQL\\\\Tests\\\\Database\\\\AuthorizeArgsTests\\\\GraphQLController\\:\\:queryContext\\(\\) has no return typehint specified\\.$#"
count: 1
path: tests/Database/AuthorizeArgsTests/GraphQLController.php

-
message: "#^Method Rebing\\\\GraphQL\\\\Tests\\\\Database\\\\AuthorizeArgsTests\\\\GraphQLController\\:\\:queryContext\\(\\) has parameter \\$variables with no value type specified in iterable type array\\.$#"
count: 1
path: tests/Database/AuthorizeArgsTests/GraphQLController.php

-
message: "#^Method Rebing\\\\GraphQL\\\\Tests\\\\Database\\\\AuthorizeArgsTests\\\\TestAuthorizationArgsQuery\\:\\:authorize\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#"
count: 1
Expand Down Expand Up @@ -1050,16 +1025,6 @@ parameters:
count: 1
path: tests/Database/SelectFields/QueryArgsAndContextTests/GraphQLContext.php

-
message: "#^Method Rebing\\\\GraphQL\\\\Tests\\\\Database\\\\SelectFields\\\\QueryArgsAndContextTests\\\\GraphQLController\\:\\:queryContext\\(\\) has no return typehint specified\\.$#"
count: 1
path: tests/Database/SelectFields/QueryArgsAndContextTests/GraphQLController.php

-
message: "#^Method Rebing\\\\GraphQL\\\\Tests\\\\Database\\\\SelectFields\\\\QueryArgsAndContextTests\\\\GraphQLController\\:\\:queryContext\\(\\) has parameter \\$variables with no value type specified in iterable type array\\.$#"
count: 1
path: tests/Database/SelectFields/QueryArgsAndContextTests/GraphQLController.php

-
message: "#^Property Rebing\\\\GraphQL\\\\Tests\\\\Database\\\\SelectFields\\\\QueryArgsAndContextTests\\\\PostType\\:\\:\\$attributes has no typehint specified\\.$#"
count: 1
Expand Down Expand Up @@ -2030,6 +1995,11 @@ parameters:
count: 1
path: tests/Unit/EngineErrorInResolverTests/QueryWithEngineErrorInCodeQuery.php

-
message: "#^Offset 'index' does not exist on array\\|null\\.$#"
count: 1
path: tests/Unit/ExecutionMiddlewareTest/ChangeVariableMiddleware.php

-
message: "#^Method Rebing\\\\GraphQL\\\\Tests\\\\Unit\\\\FieldTest\\:\\:getFieldClass\\(\\) has no return typehint specified\\.$#"
count: 1
Expand Down
Loading