diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f00657b..a05e604c 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)` or `GraphQL::appendGlobalResolverMiddleware(new YourMiddleware(...))` 2024-02-18, 9.3.0 ----------------- diff --git a/README.md b/README.md index b3f846ee..ea12dd9b 100644 --- a/README.md +++ b/README.md @@ -1151,6 +1151,28 @@ Alternatively, you can override `getMiddleware` to supply your own logic: } ``` +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 + ... + public function boot() + { + ... + GraphQL::appendGlobalResolverMiddleware(YourMiddleware::class); + // Or with new instance + GraphQL::appendGlobalResolverMiddleware(new YourMiddleware(...)); + } +``` + #### Terminable middleware Sometimes a middleware may need to do some work after the response has been sent to the browser. 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 1797a238..9499f459 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,24 @@ 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 + { + $resolverMiddlewares = $this->config->get('graphql.resolver_middleware_append') ?? []; + + return array_merge($resolverMiddlewares, $this->globalResolverMiddlewares); + } + /** * @param array $types */ @@ -501,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/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..2d36e814 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)) @@ -165,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 { diff --git a/tests/Support/Middlewares/GlobalInstanceMiddleware.php b/tests/Support/Middlewares/GlobalInstanceMiddleware.php new file mode 100644 index 00000000..4204740d --- /dev/null +++ b/tests/Support/Middlewares/GlobalInstanceMiddleware.php @@ -0,0 +1,32 @@ +invalidValue = $invalidValue; + } + + /** + * @phpstan-param mixed $root + * @phpstan-param mixed $context + */ + 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/Support/Middlewares/GlobalMiddleware.php b/tests/Support/Middlewares/GlobalMiddleware.php new file mode 100644 index 00000000..962515e2 --- /dev/null +++ b/tests/Support/Middlewares/GlobalMiddleware.php @@ -0,0 +1,18 @@ + 'Intercepted by GlobalMiddleware']]; + } +} diff --git a/tests/Unit/GlobalMiddlewareTest.php b/tests/Unit/GlobalMiddlewareTest.php new file mode 100644 index 00000000..d4965382 --- /dev/null +++ b/tests/Unit/GlobalMiddlewareTest.php @@ -0,0 +1,36 @@ +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); + } + + 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()); + } +}