From 81de520970b651c37f0e2535e57f4a0da10ed6a4 Mon Sep 17 00:00:00 2001 From: Colant Alan Date: Tue, 27 Feb 2024 10:05:54 +0100 Subject: [PATCH 1/7] Allow global resolver Middlewares --- src/GraphQL.php | 23 +++++++++++++++++++++++ src/Support/Facades/GraphQL.php | 2 ++ src/Support/Field.php | 12 +++++++++++- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/GraphQL.php b/src/GraphQL.php index 1797a238..834df01f 100644 --- a/src/GraphQL.php +++ b/src/GraphQL.php @@ -51,6 +51,13 @@ class GraphQL */ protected $types = []; + /** + * These middleware are executed before all resolve methods + * + * @var array + */ + protected $globalResolverMiddlewares = []; + /** @var Type[] */ protected $typesInstances = []; @@ -186,6 +193,22 @@ protected function appendGraphqlExecutionMiddleware(array $middlewares): array return $middlewares; } + /** + * @phpstan-param class-string|object $class + */ + public function appendGlobalResolverMiddleware(object|string $class): void + { + $this->globalResolverMiddlewares[] = $class; + } + + /** + * @phpstan-return array + */ + public function getGlobalResolverMiddlewares(): array + { + return $this->globalResolverMiddlewares; + } + /** * @param array $types */ diff --git a/src/Support/Facades/GraphQL.php b/src/Support/Facades/GraphQL.php index 5ebb80b5..e7ec6181 100644 --- a/src/Support/Facades/GraphQL.php +++ b/src/Support/Facades/GraphQL.php @@ -24,6 +24,8 @@ * @method static Schema buildSchemaFromConfig(array $schemaConfig) * @method static array getSchemas() * @method static void addSchema(string $name, Schema $schema) + * @method static array getGlobalResolverMiddlewares() + * @method static void appendGlobalResolverMiddleware(object|string $class) * @method static void addType(object|string $class, string $name = null) * @method static Type objectType(ObjectType|array|string $type, array $opts = []) * @method static array formatError(Error $e) diff --git a/src/Support/Field.php b/src/Support/Field.php index bcba164f..d3d1ddf5 100644 --- a/src/Support/Field.php +++ b/src/Support/Field.php @@ -13,6 +13,7 @@ use Rebing\GraphQL\Error\AuthorizationError; use Rebing\GraphQL\Error\ValidationError; use Rebing\GraphQL\Support\AliasArguments\AliasArguments; +use Rebing\GraphQL\Support\Facades\GraphQL; use ReflectionMethod; /** @@ -145,6 +146,15 @@ protected function getMiddleware(): array return $this->middleware; } + /** + * @return array + * @phpstan-param array $middleware + */ + protected function appendGlobalMiddlewares(array $middleware): array + { + return array_merge($middleware, GraphQL::getGlobalResolverMiddlewares()); + } + protected function getResolver(): ?Closure { $resolver = $this->originalResolver(); @@ -154,7 +164,7 @@ protected function getResolver(): ?Closure } return function ($root, ...$arguments) use ($resolver) { - $middleware = $this->getMiddleware(); + $middleware = $this->appendGlobalMiddlewares($this->getMiddleware()); return app()->make(Pipeline::class) ->send(array_merge([$this], $arguments)) From 1862281f85cb10d9520ec26c3bf13b7525b3e74c Mon Sep 17 00:00:00 2001 From: Colant Alan Date: Tue, 27 Feb 2024 10:30:11 +0100 Subject: [PATCH 2/7] allow resolver middleware to be instancied --- src/Support/Field.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Support/Field.php b/src/Support/Field.php index d3d1ddf5..2d36e814 100644 --- a/src/Support/Field.php +++ b/src/Support/Field.php @@ -147,7 +147,7 @@ protected function getMiddleware(): array } /** - * @return array + * @return array * @phpstan-param array $middleware */ protected function appendGlobalMiddlewares(array $middleware): array @@ -175,7 +175,7 @@ protected function getResolver(): ?Closure foreach ($middleware as $name) { /** @var Middleware $instance */ - $instance = app()->make($name); + $instance = \is_object($name) ? $name : app()->make($name); if (method_exists($instance, 'terminate')) { app()->terminating(function () use ($arguments, $instance, $result): void { From ad800401f045545d87653c9ed70af3b8ebb2b14b Mon Sep 17 00:00:00 2001 From: Colant Alan Date: Tue, 27 Feb 2024 15:27:33 +0100 Subject: [PATCH 3/7] add tests and update md --- CHANGELOG.md | 9 +++++++-- README.md | 10 ++++++++++ .../Support/Middlewares/GlobalMiddleware.php | 19 +++++++++++++++++++ tests/Unit/MiddlewareTest.php | 14 ++++++++++++++ 4 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 tests/Support/Middlewares/GlobalMiddleware.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f00657b..c485aec7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,13 @@ CHANGELOG ========= -[Next release](https://github.com/rebing/graphql-laravel/compare/9.3.0...master) --------------- +[Next release](https://github.com/rebing/graphql-laravel/compare/9.4.0...master) + +2024-02-27, 9.4.0 +----------------- + +## Added +- Possibility to add resolver middleware at runtime using `GraphQL::appendGlobalResolverMiddleware(YourMiddleware::class)` 2024-02-18, 9.3.0 ----------------- diff --git a/README.md b/README.md index b3f846ee..f86d96cb 100644 --- a/README.md +++ b/README.md @@ -1150,6 +1150,16 @@ Alternatively, you can override `getMiddleware` to supply your own logic: return array_merge([...], $this->middleware); } ``` +If you want to register middleware globally, use can use the `appendGlobalResolverMiddleware` method in ServiceProvider: + +```php + ... + public function boot() + { + ... + GraphQL::appendGlobalResolverMiddleware(YourMiddleware::class); + } +``` #### Terminable middleware diff --git a/tests/Support/Middlewares/GlobalMiddleware.php b/tests/Support/Middlewares/GlobalMiddleware.php new file mode 100644 index 00000000..3beb89d8 --- /dev/null +++ b/tests/Support/Middlewares/GlobalMiddleware.php @@ -0,0 +1,19 @@ +errors[0]->getMessage()); } + + public function testGlobalMiddlewareExecuted(): void + { + $mock = $this->partialMock(GlobalMiddleware::class); + GraphQL::appendGlobalResolverMiddleware($mock); + $result = GraphQL::queryAndReturnResult($this->queries['exampleMiddleware'], [ + 'index' => 2, + ]); + $mock->shouldHaveReceived('handle'); + + self::assertObjectHasProperty('errors', $result); + self::assertSame('Example 3 is not allowed', $result->errors[0]->getMessage()); + } } From 803f4bf4f89f1ae00e0cc65190116849e5d2027a Mon Sep 17 00:00:00 2001 From: Colant Alan Date: Mon, 4 Mar 2024 11:12:47 +0100 Subject: [PATCH 4/7] allow defining using configuration file --- README.md | 12 ++++++++- config/config.php | 5 ++++ src/GraphQL.php | 6 +++-- .../Support/Middlewares/GlobalMiddleware.php | 2 +- tests/Unit/GlobalMiddlewareTest.php | 27 +++++++++++++++++++ tests/Unit/MiddlewareTest.php | 14 ---------- 6 files changed, 48 insertions(+), 18 deletions(-) create mode 100644 tests/Unit/GlobalMiddlewareTest.php diff --git a/README.md b/README.md index f86d96cb..601650c8 100644 --- a/README.md +++ b/README.md @@ -1150,7 +1150,17 @@ Alternatively, you can override `getMiddleware` to supply your own logic: return array_merge([...], $this->middleware); } ``` -If you want to register middleware globally, use can use the `appendGlobalResolverMiddleware` method in ServiceProvider: + +If you want to register middleware globally, use the `resolver_middleware_append` key in `config/graphql.php`: + +```php +return [ + ... + resolver_middleware_append => [YourMiddleware::class], +]; +``` + +You can also use the `appendGlobalResolverMiddleware` method in any ServiceProvider: ```php ... diff --git a/config/config.php b/config/config.php index 5185db30..739cad23 100644 --- a/config/config.php +++ b/config/config.php @@ -214,4 +214,9 @@ Rebing\GraphQL\Support\ExecutionMiddleware\AddAuthUserContextValueMiddleware::class, // \Rebing\GraphQL\Support\ExecutionMiddleware\UnusedVariablesMiddleware::class, ], + + /* + * Globally registered ResolverMiddleware + */ + 'resolver_middleware_append' => null, ]; diff --git a/src/GraphQL.php b/src/GraphQL.php index 834df01f..9499f459 100644 --- a/src/GraphQL.php +++ b/src/GraphQL.php @@ -206,7 +206,9 @@ public function appendGlobalResolverMiddleware(object|string $class): void */ public function getGlobalResolverMiddlewares(): array { - return $this->globalResolverMiddlewares; + $resolverMiddlewares = $this->config->get('graphql.resolver_middleware_append') ?? []; + + return array_merge($resolverMiddlewares, $this->globalResolverMiddlewares); } /** @@ -524,8 +526,8 @@ public function wrapType(string $typeName, string $customTypeName, string $wrapp } /** - * @see \GraphQL\Executor\ExecutionResult::setErrorFormatter * @return array + * @see \GraphQL\Executor\ExecutionResult::setErrorFormatter */ public static function formatError(Error $e): array { diff --git a/tests/Support/Middlewares/GlobalMiddleware.php b/tests/Support/Middlewares/GlobalMiddleware.php index 3beb89d8..4cd2740c 100644 --- a/tests/Support/Middlewares/GlobalMiddleware.php +++ b/tests/Support/Middlewares/GlobalMiddleware.php @@ -14,6 +14,6 @@ class GlobalMiddleware extends Middleware */ public function handle($root, array $args, $context, ResolveInfo $info, Closure $next): mixed { - return parent::handle($root, $args, $context, $info, $next); + return [['test' => 'Intercepted by GlobalMiddleware']]; } } diff --git a/tests/Unit/GlobalMiddlewareTest.php b/tests/Unit/GlobalMiddlewareTest.php new file mode 100644 index 00000000..92639251 --- /dev/null +++ b/tests/Unit/GlobalMiddlewareTest.php @@ -0,0 +1,27 @@ +app['config']->set('graphql.resolver_middleware_append', [ + GlobalMiddleware::class, + ]); + $result = GraphQL::queryAndReturnResult($this->queries['examples']); + self::assertSame(['examples' => [['test' => 'Intercepted by GlobalMiddleware']]], $result->data); + } + + public function testFacadeGlobalMiddlewareExecuted(): void + { + GraphQL::appendGlobalResolverMiddleware(GlobalMiddleware::class); + $result = GraphQL::queryAndReturnResult($this->queries['examples']); + self::assertSame(['examples' => [['test' => 'Intercepted by GlobalMiddleware']]], $result->data); + } +} diff --git a/tests/Unit/MiddlewareTest.php b/tests/Unit/MiddlewareTest.php index 32121414..e7f5d951 100644 --- a/tests/Unit/MiddlewareTest.php +++ b/tests/Unit/MiddlewareTest.php @@ -4,7 +4,6 @@ namespace Rebing\GraphQL\Tests\Unit; use Rebing\GraphQL\Support\Facades\GraphQL; -use Rebing\GraphQL\Tests\Support\Middlewares\GlobalMiddleware; use Rebing\GraphQL\Tests\TestCase; class MiddlewareTest extends TestCase @@ -80,17 +79,4 @@ public function testMiddlewareTerminateHappensAfterResponseIsSent(): void self::assertObjectHasProperty('errors', $result); self::assertMatchesRegularExpression('/^Undefined .* 6$/', $result->errors[0]->getMessage()); } - - public function testGlobalMiddlewareExecuted(): void - { - $mock = $this->partialMock(GlobalMiddleware::class); - GraphQL::appendGlobalResolverMiddleware($mock); - $result = GraphQL::queryAndReturnResult($this->queries['exampleMiddleware'], [ - 'index' => 2, - ]); - $mock->shouldHaveReceived('handle'); - - self::assertObjectHasProperty('errors', $result); - self::assertSame('Example 3 is not allowed', $result->errors[0]->getMessage()); - } } From db329a670ffa814165db3183bf4fed74ca3155d3 Mon Sep 17 00:00:00 2001 From: Colant Alan Date: Mon, 4 Mar 2024 11:50:35 +0100 Subject: [PATCH 5/7] allow instance as resolver middleware --- CHANGELOG.md | 2 +- README.md | 2 ++ .../Middlewares/GlobalInstanceMiddleware.php | 33 +++++++++++++++++++ tests/Unit/GlobalMiddlewareTest.php | 9 +++++ 4 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 tests/Support/Middlewares/GlobalInstanceMiddleware.php diff --git a/CHANGELOG.md b/CHANGELOG.md index c485aec7..a05e604c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ CHANGELOG ----------------- ## Added -- Possibility to add resolver middleware at runtime using `GraphQL::appendGlobalResolverMiddleware(YourMiddleware::class)` +- Possibility to add resolver middleware at runtime using `GraphQL::appendGlobalResolverMiddleware(YourMiddleware::class)` or `GraphQL::appendGlobalResolverMiddleware(new YourMiddleware(...))` 2024-02-18, 9.3.0 ----------------- diff --git a/README.md b/README.md index 601650c8..6576cdfc 100644 --- a/README.md +++ b/README.md @@ -1168,6 +1168,8 @@ You can also use the `appendGlobalResolverMiddleware` method in any ServiceProvi { ... GraphQL::appendGlobalResolverMiddleware(YourMiddleware::class); + // Or with new instance + GraphQL::appendGlobalResolverMiddleware(new YourMiddleware(...)); } ``` diff --git a/tests/Support/Middlewares/GlobalInstanceMiddleware.php b/tests/Support/Middlewares/GlobalInstanceMiddleware.php new file mode 100644 index 00000000..d5b01d5f --- /dev/null +++ b/tests/Support/Middlewares/GlobalInstanceMiddleware.php @@ -0,0 +1,33 @@ +invalidValue = $invalidValue; + } + + /** + * @phpstan-param mixed $root + * @phpstan-param mixed $context + * @phpstan-return mixed + */ + public function handle($root, array $args, $context, ResolveInfo $info, Closure $next): mixed + { + if (isset($this->invalidValue) && $this->invalidValue === $args['index']) { + throw new Exception('Index is not allowed'); + } + + return $next($root, $args, $context, $info); + } +} diff --git a/tests/Unit/GlobalMiddlewareTest.php b/tests/Unit/GlobalMiddlewareTest.php index 92639251..d4965382 100644 --- a/tests/Unit/GlobalMiddlewareTest.php +++ b/tests/Unit/GlobalMiddlewareTest.php @@ -4,6 +4,7 @@ namespace Rebing\GraphQL\Tests\Unit; use Rebing\GraphQL\Support\Facades\GraphQL; +use Rebing\GraphQL\Tests\Support\Middlewares\GlobalInstanceMiddleware; use Rebing\GraphQL\Tests\Support\Middlewares\GlobalMiddleware; use Rebing\GraphQL\Tests\TestCase; @@ -24,4 +25,12 @@ public function testFacadeGlobalMiddlewareExecuted(): void $result = GraphQL::queryAndReturnResult($this->queries['examples']); self::assertSame(['examples' => [['test' => 'Intercepted by GlobalMiddleware']]], $result->data); } + + public function testCanUseMiddlewareInstance(): void + { + GraphQL::appendGlobalResolverMiddleware(new GlobalInstanceMiddleware(0)); + $result = GraphQL::queryAndReturnResult($this->queries['examplesWithVariables'], ['index' => 0]); + self::assertObjectHasProperty('errors', $result); + self::assertSame('Index is not allowed', $result->errors[0]->getMessage()); + } } From 8888892412d4052d0f203d3e585045c8ff37ca2a Mon Sep 17 00:00:00 2001 From: Markus Podar Date: Mon, 4 Mar 2024 20:20:09 +0100 Subject: [PATCH 6/7] Remove redundant return type The actual type _is_ mixed --- tests/Support/Middlewares/GlobalInstanceMiddleware.php | 1 - tests/Support/Middlewares/GlobalMiddleware.php | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/Support/Middlewares/GlobalInstanceMiddleware.php b/tests/Support/Middlewares/GlobalInstanceMiddleware.php index d5b01d5f..4204740d 100644 --- a/tests/Support/Middlewares/GlobalInstanceMiddleware.php +++ b/tests/Support/Middlewares/GlobalInstanceMiddleware.php @@ -20,7 +20,6 @@ public function __construct(int $invalidValue) /** * @phpstan-param mixed $root * @phpstan-param mixed $context - * @phpstan-return mixed */ public function handle($root, array $args, $context, ResolveInfo $info, Closure $next): mixed { diff --git a/tests/Support/Middlewares/GlobalMiddleware.php b/tests/Support/Middlewares/GlobalMiddleware.php index 4cd2740c..962515e2 100644 --- a/tests/Support/Middlewares/GlobalMiddleware.php +++ b/tests/Support/Middlewares/GlobalMiddleware.php @@ -10,7 +10,6 @@ class GlobalMiddleware extends Middleware /** * @phpstan-param mixed $root * @phpstan-param mixed $context - * @phpstan-return mixed */ public function handle($root, array $args, $context, ResolveInfo $info, Closure $next): mixed { From 66056f1ab748514fd3e0e956435162f35e3ee9e7 Mon Sep 17 00:00:00 2001 From: Markus Podar Date: Mon, 4 Mar 2024 20:20:23 +0100 Subject: [PATCH 7/7] Quote string key in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6576cdfc..ea12dd9b 100644 --- a/README.md +++ b/README.md @@ -1156,7 +1156,7 @@ If you want to register middleware globally, use the `resolver_middleware_append ```php return [ ... - resolver_middleware_append => [YourMiddleware::class], + 'resolver_middleware_append' => [YourMiddleware::class], ]; ```