diff --git a/src/GraphQL.php b/src/GraphQL.php index 3bf211e3..bfa174fb 100644 --- a/src/GraphQL.php +++ b/src/GraphQL.php @@ -19,6 +19,7 @@ use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Pipeline\Pipeline; use Illuminate\Support\Facades\Config; +use Illuminate\Support\Facades\Route; use Illuminate\Support\Traits\Macroable; use Illuminate\Validation\ValidationException; use Rebing\GraphQL\Error\AuthorizationError; @@ -657,6 +658,24 @@ public static function getNormalizedSchemaConfiguration(string $schemaName): arr return $schemaConfig; } + public static function parseRoute(string $schemaName, array $schemaConfig, array $routeConfig, ?string $alias = null): \Illuminate\Routing\Route + { + if (null !== $alias) { + $routeName = $alias ? ".$alias" : ''; + } else { + $routeName = $schemaName ? ".$schemaName" : ''; + } + + return Route::match( + $schemaConfig['method'] ?? ['GET', 'POST'], + $alias ?? $schemaName, + $schemaConfig['controller'] ?? $routeConfig['controller'] ?? [GraphQLController::class, 'query'], + )->middleware(array_merge( + [GraphQLHttpMiddleware::class . ":$schemaName"], + $schemaConfig['middleware'] ?? [] + ))->name($routeName); + } + public function decorateExecutionResult(ExecutionResult $executionResult): ExecutionResult { $errorFormatter = $this->config->get('graphql.error_formatter', [static::class, 'formatError']); diff --git a/src/GraphQLController.php b/src/GraphQLController.php index 62cfc41b..57ca94a6 100644 --- a/src/GraphQLController.php +++ b/src/GraphQLController.php @@ -8,7 +8,6 @@ use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Routing\Controller; -use Illuminate\Support\Str; use Laragraph\Utils\RequestParser; use Rebing\GraphQL\Support\OperationParams; @@ -16,9 +15,7 @@ class GraphQLController extends Controller { public function query(Request $request, RequestParser $parser, Repository $config, GraphQL $graphql): JsonResponse { - $routePrefix = $config->get('graphql.route.prefix', 'graphql'); - $schemaName = $this->findSchemaNameInRequest($request, "/$routePrefix") ?: $config->get('graphql.default_schema', 'default'); - + $schemaName = $request->server('graphql.schemaName'); $operations = $parser->parseRequest($request); $headers = $config->get('graphql.headers', []); @@ -73,15 +70,4 @@ protected function createBatchingNotSupportedResponse(array $input): array return $data; } - - protected function findSchemaNameInRequest(Request $request, string $routePrefix): ?string - { - $path = $request->getPathInfo(); - - if (!Str::startsWith($path, $routePrefix)) { - return null; - } - - return trim(Str::after($path, $routePrefix), '/'); - } } diff --git a/src/GraphQLHttpMiddleware.php b/src/GraphQLHttpMiddleware.php new file mode 100644 index 00000000..d9f27087 --- /dev/null +++ b/src/GraphQLHttpMiddleware.php @@ -0,0 +1,22 @@ +server->set('graphql.schemaName', $schemaName); + + return $next($request); + } +} diff --git a/src/routes.php b/src/routes.php index 0a3e0d19..228df39e 100644 --- a/src/routes.php +++ b/src/routes.php @@ -2,63 +2,27 @@ declare(strict_types = 1); -use Illuminate\Container\Container; -use Illuminate\Contracts\Config\Repository; -use Illuminate\Routing\Router; +use Illuminate\Support\Facades\Route; use Rebing\GraphQL\GraphQL; -use Rebing\GraphQL\GraphQLController; -/** @var Repository $config */ -$config = Container::getInstance()->make(Repository::class); +$routeConfig = config('graphql.route'); -$routeConfig = $config->get('graphql.route'); - -if ($routeConfig) { - /** @var Router $router */ - $router = app('router'); - - $routeGroupAttributes = array_merge( - [ - 'prefix' => $routeConfig['prefix'] ?? 'graphql', - 'middleware' => $routeConfig['middleware'] ?? [], - ], - $routeConfig['group_attributes'] ?? [] - ); - - $router->group( - $routeGroupAttributes, - function (Router $router) use ($config, $routeConfig): void { - $schemas = GraphQL::getNormalizedSchemasConfiguration(); - $defaultSchema = $config->get('graphql.default_schema', 'default'); - - foreach ($schemas as $schemaName => $schemaConfig) { - $method = $schemaConfig['method'] ?? ['GET', 'POST']; - $actions = array_filter([ - 'uses' => $schemaConfig['controller'] ?? $routeConfig['controller'] ?? GraphQLController::class . '@query', - 'middleware' => $schemaConfig['middleware'] ?? $routeConfig['middleware'] ?? null, - ]); - - // Support array syntax: `[Some::class, 'method']` - if (\is_array($actions['uses']) && isset($actions['uses'][0], $actions['uses'][1])) { - $actions['uses'] = $actions['uses'][0] . '@' . $actions['uses'][1]; - } - - // Add route for each schema… - $router->addRoute( - $method, - $schemaName, - $actions + ['as' => "graphql.$schemaName"] - ); +if (empty($routeConfig)) { + return; +} - // … and the default schema against the group itself - if ($schemaName === $defaultSchema) { - $router->addRoute( - $method, - '', - $actions + ['as' => 'graphql'] - ); - } - } +$defaultSchemaName = config('graphql.default_schema', 'default'); +Route::group([ + 'prefix' => $routeConfig['prefix'] ?? 'graphql', + 'middleware' => $routeConfig['middleware'] ?? [], + 'as' => 'graphql', + ...$routeConfig['group_attributes'] ?? [], +], function () use (&$defaultSchemaName, &$routeConfig): void { + foreach (GraphQL::getNormalizedSchemasConfiguration() as $schemaName => $schemaConfig) { + GraphQL::parseRoute($schemaName, $schemaConfig, $routeConfig); + + if ($schemaName === $defaultSchemaName) { + GraphQL::parseRoute($schemaName, $schemaConfig, $routeConfig, ''); } - ); -} + } +}); diff --git a/tests/Unit/EndpointParamsTest.php b/tests/Unit/EndpointParamsTest.php new file mode 100644 index 00000000..32cb9d66 --- /dev/null +++ b/tests/Unit/EndpointParamsTest.php @@ -0,0 +1,42 @@ +call('GET', '/graphql/arbitrary_param', [ + 'query' => $this->queries['examples'], + ]); + + self::assertEquals(200, $response->getStatusCode()); + + $content = $response->getData(true); + self::assertArrayHasKey('data', $content); + self::assertEquals($content['data'], [ + 'examples' => $this->data, + ]); + } + public function testGetCustomSchemaWithRouteParameter(): void + { + $response = $this->call('GET', '/graphql/arbitrary_param/custom', [ + 'query' => $this->queries['examplesCustom'], + ]); + self::assertEquals(200, $response->getStatusCode()); + $content = $response->getData(true); + self::assertArrayHasKey('data', $content); + self::assertEquals($content['data'], [ + 'examplesCustom' => $this->data, + ]); + } + + protected function getEnvironmentSetUp($app): void + { + parent::getEnvironmentSetUp($app); + $app['config']->set('graphql.route.prefix', 'graphql/{parameter}'); + } +} diff --git a/tests/Unit/RoutesTest.php b/tests/Unit/RoutesTest.php index 30d2c6ed..9c8d3586 100644 --- a/tests/Unit/RoutesTest.php +++ b/tests/Unit/RoutesTest.php @@ -5,6 +5,7 @@ use Illuminate\Routing\Route; use Illuminate\Support\Collection; +use Rebing\GraphQL\GraphQLHttpMiddleware; use Rebing\GraphQL\Tests\Support\Objects\ExampleMiddleware; use Rebing\GraphQL\Tests\Support\Objects\ExampleSchema; use Rebing\GraphQL\Tests\Support\Objects\ExampleSchemaWithMethod; @@ -51,6 +52,7 @@ public function testRoutes(): void ], 'uri' => 'graphql_test', 'middleware' => [ + GraphQLHttpMiddleware::class . ':default', ExampleMiddleware::class, ], ], @@ -62,6 +64,7 @@ public function testRoutes(): void ], 'uri' => 'graphql_test/default', 'middleware' => [ + GraphQLHttpMiddleware::class . ':default', ExampleMiddleware::class, ], ], @@ -73,6 +76,7 @@ public function testRoutes(): void ], 'uri' => 'graphql_test/custom', 'middleware' => [ + GraphQLHttpMiddleware::class . ':custom', ExampleMiddleware::class, ], ], @@ -82,6 +86,7 @@ public function testRoutes(): void ], 'uri' => 'graphql_test/with_methods', 'middleware' => [ + GraphQLHttpMiddleware::class . ':with_methods', ExampleMiddleware::class, ], ], @@ -93,6 +98,7 @@ public function testRoutes(): void ], 'uri' => 'graphql_test/class_based', 'middleware' => [ + GraphQLHttpMiddleware::class . ':class_based', ExampleMiddleware::class, ], ], @@ -102,6 +108,7 @@ public function testRoutes(): void ], 'uri' => 'graphql_test/class_based_with_methods', 'middleware' => [ + GraphQLHttpMiddleware::class . ':class_based_with_methods', ExampleMiddleware::class, ], ],