Skip to content

Commit 91735a9

Browse files
authored
Merge pull request #1127 from alancolant/global-resolver-middleware
Allow global resolver Middlewares
2 parents 838b89e + 66056f1 commit 91735a9

File tree

9 files changed

+160
-5
lines changed

9 files changed

+160
-5
lines changed

CHANGELOG.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
CHANGELOG
22
=========
33

4-
[Next release](https://github.com/rebing/graphql-laravel/compare/9.3.0...master)
5-
--------------
4+
[Next release](https://github.com/rebing/graphql-laravel/compare/9.4.0...master)
5+
6+
2024-02-27, 9.4.0
7+
-----------------
8+
9+
## Added
10+
- Possibility to add resolver middleware at runtime using `GraphQL::appendGlobalResolverMiddleware(YourMiddleware::class)` or `GraphQL::appendGlobalResolverMiddleware(new YourMiddleware(...))`
611

712
2024-02-18, 9.3.0
813
-----------------

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,6 +1151,28 @@ Alternatively, you can override `getMiddleware` to supply your own logic:
11511151
}
11521152
```
11531153

1154+
If you want to register middleware globally, use the `resolver_middleware_append` key in `config/graphql.php`:
1155+
1156+
```php
1157+
return [
1158+
...
1159+
'resolver_middleware_append' => [YourMiddleware::class],
1160+
];
1161+
```
1162+
1163+
You can also use the `appendGlobalResolverMiddleware` method in any ServiceProvider:
1164+
1165+
```php
1166+
...
1167+
public function boot()
1168+
{
1169+
...
1170+
GraphQL::appendGlobalResolverMiddleware(YourMiddleware::class);
1171+
// Or with new instance
1172+
GraphQL::appendGlobalResolverMiddleware(new YourMiddleware(...));
1173+
}
1174+
```
1175+
11541176
#### Terminable middleware
11551177

11561178
Sometimes a middleware may need to do some work after the response has been sent to the browser.

config/config.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,4 +214,9 @@
214214
Rebing\GraphQL\Support\ExecutionMiddleware\AddAuthUserContextValueMiddleware::class,
215215
// \Rebing\GraphQL\Support\ExecutionMiddleware\UnusedVariablesMiddleware::class,
216216
],
217+
218+
/*
219+
* Globally registered ResolverMiddleware
220+
*/
221+
'resolver_middleware_append' => null,
217222
];

src/GraphQL.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ class GraphQL
5151
*/
5252
protected $types = [];
5353

54+
/**
55+
* These middleware are executed before all resolve methods
56+
*
57+
* @var array<object|class-string>
58+
*/
59+
protected $globalResolverMiddlewares = [];
60+
5461
/** @var Type[] */
5562
protected $typesInstances = [];
5663

@@ -186,6 +193,24 @@ protected function appendGraphqlExecutionMiddleware(array $middlewares): array
186193
return $middlewares;
187194
}
188195

196+
/**
197+
* @phpstan-param class-string|object $class
198+
*/
199+
public function appendGlobalResolverMiddleware(object|string $class): void
200+
{
201+
$this->globalResolverMiddlewares[] = $class;
202+
}
203+
204+
/**
205+
* @phpstan-return array<object|class-string>
206+
*/
207+
public function getGlobalResolverMiddlewares(): array
208+
{
209+
$resolverMiddlewares = $this->config->get('graphql.resolver_middleware_append') ?? [];
210+
211+
return array_merge($resolverMiddlewares, $this->globalResolverMiddlewares);
212+
}
213+
189214
/**
190215
* @param array<int|string,string> $types
191216
*/
@@ -501,8 +526,8 @@ public function wrapType(string $typeName, string $customTypeName, string $wrapp
501526
}
502527

503528
/**
504-
* @see \GraphQL\Executor\ExecutionResult::setErrorFormatter
505529
* @return array<string,mixed>
530+
* @see \GraphQL\Executor\ExecutionResult::setErrorFormatter
506531
*/
507532
public static function formatError(Error $e): array
508533
{

src/Support/Facades/GraphQL.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
* @method static Schema buildSchemaFromConfig(array $schemaConfig)
2525
* @method static array getSchemas()
2626
* @method static void addSchema(string $name, Schema $schema)
27+
* @method static array getGlobalResolverMiddlewares()
28+
* @method static void appendGlobalResolverMiddleware(object|string $class)
2729
* @method static void addType(object|string $class, string $name = null)
2830
* @method static Type objectType(ObjectType|array|string $type, array $opts = [])
2931
* @method static array formatError(Error $e)

src/Support/Field.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Rebing\GraphQL\Error\AuthorizationError;
1414
use Rebing\GraphQL\Error\ValidationError;
1515
use Rebing\GraphQL\Support\AliasArguments\AliasArguments;
16+
use Rebing\GraphQL\Support\Facades\GraphQL;
1617
use ReflectionMethod;
1718

1819
/**
@@ -145,6 +146,15 @@ protected function getMiddleware(): array
145146
return $this->middleware;
146147
}
147148

149+
/**
150+
* @return array<class-string|object>
151+
* @phpstan-param array<string> $middleware
152+
*/
153+
protected function appendGlobalMiddlewares(array $middleware): array
154+
{
155+
return array_merge($middleware, GraphQL::getGlobalResolverMiddlewares());
156+
}
157+
148158
protected function getResolver(): ?Closure
149159
{
150160
$resolver = $this->originalResolver();
@@ -154,7 +164,7 @@ protected function getResolver(): ?Closure
154164
}
155165

156166
return function ($root, ...$arguments) use ($resolver) {
157-
$middleware = $this->getMiddleware();
167+
$middleware = $this->appendGlobalMiddlewares($this->getMiddleware());
158168

159169
return app()->make(Pipeline::class)
160170
->send(array_merge([$this], $arguments))
@@ -165,7 +175,7 @@ protected function getResolver(): ?Closure
165175

166176
foreach ($middleware as $name) {
167177
/** @var Middleware $instance */
168-
$instance = app()->make($name);
178+
$instance = \is_object($name) ? $name : app()->make($name);
169179

170180
if (method_exists($instance, 'terminate')) {
171181
app()->terminating(function () use ($arguments, $instance, $result): void {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
namespace Rebing\GraphQL\Tests\Support\Middlewares;
5+
6+
use Closure;
7+
use Exception;
8+
use GraphQL\Type\Definition\ResolveInfo;
9+
use Rebing\GraphQL\Support\Middleware;
10+
11+
class GlobalInstanceMiddleware extends Middleware
12+
{
13+
protected int $invalidValue;
14+
15+
public function __construct(int $invalidValue)
16+
{
17+
$this->invalidValue = $invalidValue;
18+
}
19+
20+
/**
21+
* @phpstan-param mixed $root
22+
* @phpstan-param mixed $context
23+
*/
24+
public function handle($root, array $args, $context, ResolveInfo $info, Closure $next): mixed
25+
{
26+
if (isset($this->invalidValue) && $this->invalidValue === $args['index']) {
27+
throw new Exception('Index is not allowed');
28+
}
29+
30+
return $next($root, $args, $context, $info);
31+
}
32+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php declare(strict_types = 1);
2+
namespace Rebing\GraphQL\Tests\Support\Middlewares;
3+
4+
use Closure;
5+
use GraphQL\Type\Definition\ResolveInfo;
6+
use Rebing\GraphQL\Support\Middleware;
7+
8+
class GlobalMiddleware extends Middleware
9+
{
10+
/**
11+
* @phpstan-param mixed $root
12+
* @phpstan-param mixed $context
13+
*/
14+
public function handle($root, array $args, $context, ResolveInfo $info, Closure $next): mixed
15+
{
16+
return [['test' => 'Intercepted by GlobalMiddleware']];
17+
}
18+
}

tests/Unit/GlobalMiddlewareTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
namespace Rebing\GraphQL\Tests\Unit;
5+
6+
use Rebing\GraphQL\Support\Facades\GraphQL;
7+
use Rebing\GraphQL\Tests\Support\Middlewares\GlobalInstanceMiddleware;
8+
use Rebing\GraphQL\Tests\Support\Middlewares\GlobalMiddleware;
9+
use Rebing\GraphQL\Tests\TestCase;
10+
11+
class GlobalMiddlewareTest extends TestCase
12+
{
13+
public function testConfigGlobalMiddlewareExecuted(): void
14+
{
15+
$this->app['config']->set('graphql.resolver_middleware_append', [
16+
GlobalMiddleware::class,
17+
]);
18+
$result = GraphQL::queryAndReturnResult($this->queries['examples']);
19+
self::assertSame(['examples' => [['test' => 'Intercepted by GlobalMiddleware']]], $result->data);
20+
}
21+
22+
public function testFacadeGlobalMiddlewareExecuted(): void
23+
{
24+
GraphQL::appendGlobalResolverMiddleware(GlobalMiddleware::class);
25+
$result = GraphQL::queryAndReturnResult($this->queries['examples']);
26+
self::assertSame(['examples' => [['test' => 'Intercepted by GlobalMiddleware']]], $result->data);
27+
}
28+
29+
public function testCanUseMiddlewareInstance(): void
30+
{
31+
GraphQL::appendGlobalResolverMiddleware(new GlobalInstanceMiddleware(0));
32+
$result = GraphQL::queryAndReturnResult($this->queries['examplesWithVariables'], ['index' => 0]);
33+
self::assertObjectHasProperty('errors', $result);
34+
self::assertSame('Index is not allowed', $result->errors[0]->getMessage());
35+
}
36+
}

0 commit comments

Comments
 (0)