vendor/api-platform/core/src/GraphQl/Action/EntrypointAction.php line 42

  1. <?php
  2. /*
  3.  * This file is part of the API Platform project.
  4.  *
  5.  * (c) Kévin Dunglas <dunglas@gmail.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. declare(strict_types=1);
  11. namespace ApiPlatform\GraphQl\Action;
  12. use ApiPlatform\GraphQl\Error\ErrorHandlerInterface;
  13. use ApiPlatform\GraphQl\ExecutorInterface;
  14. use ApiPlatform\GraphQl\Type\SchemaBuilderInterface;
  15. use GraphQL\Error\DebugFlag;
  16. use GraphQL\Error\Error;
  17. use GraphQL\Executor\ExecutionResult;
  18. use Symfony\Component\HttpFoundation\JsonResponse;
  19. use Symfony\Component\HttpFoundation\Request;
  20. use Symfony\Component\HttpFoundation\Response;
  21. use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
  22. use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
  23. /**
  24.  * GraphQL API entrypoint.
  25.  *
  26.  * @author Alan Poulain <contact@alanpoulain.eu>
  27.  */
  28. final class EntrypointAction
  29. {
  30.     private int $debug;
  31.     public function __construct(private readonly SchemaBuilderInterface $schemaBuilder, private readonly ExecutorInterface $executor, private readonly ?GraphiQlAction $graphiQlAction, private readonly ?GraphQlPlaygroundAction $graphQlPlaygroundAction, private readonly NormalizerInterface $normalizer, private readonly ErrorHandlerInterface $errorHandlerbool $debug false, private readonly bool $graphiqlEnabled false, private readonly bool $graphQlPlaygroundEnabled false, private readonly ?string $defaultIde null)
  32.     {
  33.         $this->debug $debug DebugFlag::INCLUDE_DEBUG_MESSAGE DebugFlag::INCLUDE_TRACE DebugFlag::NONE;
  34.     }
  35.     public function __invoke(Request $request): Response
  36.     {
  37.         try {
  38.             if ($request->isMethod('GET') && 'html' === $request->getRequestFormat()) {
  39.                 if ('graphiql' === $this->defaultIde && $this->graphiqlEnabled && $this->graphiQlAction) {
  40.                     return ($this->graphiQlAction)($request);
  41.                 }
  42.                 if ('graphql-playground' === $this->defaultIde && $this->graphQlPlaygroundEnabled && $this->graphQlPlaygroundAction) {
  43.                     return ($this->graphQlPlaygroundAction)($request);
  44.                 }
  45.             }
  46.             [$query$operationName$variables] = $this->parseRequest($request);
  47.             if (null === $query) {
  48.                 throw new BadRequestHttpException('GraphQL query is not valid.');
  49.             }
  50.             $executionResult $this->executor
  51.                 ->executeQuery($this->schemaBuilder->getSchema(), $querynullnull$variables$operationName)
  52.                 ->setErrorsHandler($this->errorHandler)
  53.                 ->setErrorFormatter($this->normalizer->normalize(...));
  54.         } catch (\Exception $exception) {
  55.             $executionResult = (new ExecutionResult(null, [new Error($exception->getMessage(), nullnull, [], null$exception)]))
  56.                 ->setErrorsHandler($this->errorHandler)
  57.                 ->setErrorFormatter($this->normalizer->normalize(...));
  58.         }
  59.         return new JsonResponse($executionResult->toArray($this->debug));
  60.     }
  61.     /**
  62.      * @throws BadRequestHttpException
  63.      */
  64.     private function parseRequest(Request $request): array
  65.     {
  66.         $queryParameters $request->query->all();
  67.         $query $queryParameters['query'] ?? null;
  68.         $operationName $queryParameters['operationName'] ?? null;
  69.         if ($variables $queryParameters['variables'] ?? []) {
  70.             $variables $this->decodeVariables($variables);
  71.         }
  72.         if (!$request->isMethod('POST')) {
  73.             return [$query$operationName$variables];
  74.         }
  75.         $contentType method_exists(Request::class, 'getContentTypeFormat') ? $request->getContentTypeFormat() : $request->getContentType();
  76.         if ('json' === $contentType) {
  77.             return $this->parseData($query$operationName$variables$request->getContent());
  78.         }
  79.         if ('graphql' === $contentType) {
  80.             $query $request->getContent();
  81.         }
  82.         if (\in_array($contentType, ['multipart''form'], true)) {
  83.             return $this->parseMultipartRequest($query$operationName$variables$request->request->all(), $request->files->all());
  84.         }
  85.         return [$query$operationName$variables];
  86.     }
  87.     /**
  88.      * @throws BadRequestHttpException
  89.      */
  90.     private function parseData(?string $query, ?string $operationName, array $variablesstring $jsonContent): array
  91.     {
  92.         if (!\is_array($data json_decode($jsonContenttrue512\JSON_ERROR_NONE))) {
  93.             throw new BadRequestHttpException('GraphQL data is not valid JSON.');
  94.         }
  95.         if (isset($data['query'])) {
  96.             $query $data['query'];
  97.         }
  98.         if (isset($data['variables'])) {
  99.             $variables \is_array($data['variables']) ? $data['variables'] : $this->decodeVariables($data['variables']);
  100.         }
  101.         if (isset($data['operationName'])) {
  102.             $operationName $data['operationName'];
  103.         }
  104.         return [$query$operationName$variables];
  105.     }
  106.     /**
  107.      * @throws BadRequestHttpException
  108.      */
  109.     private function parseMultipartRequest(?string $query, ?string $operationName, array $variables, array $bodyParameters, array $files): array
  110.     {
  111.         if ((null === $operations $bodyParameters['operations'] ?? null) || (null === $map $bodyParameters['map'] ?? null)) {
  112.             throw new BadRequestHttpException('GraphQL multipart request does not respect the specification.');
  113.         }
  114.         [$query$operationName$variables] = $this->parseData($query$operationName$variables$operations);
  115.         /** @var string $map */
  116.         if (!\is_array($decodedMap json_decode($maptrue512\JSON_ERROR_NONE))) {
  117.             throw new BadRequestHttpException('GraphQL multipart request map is not valid JSON.');
  118.         }
  119.         $variables $this->applyMapToVariables($decodedMap$variables$files);
  120.         return [$query$operationName$variables];
  121.     }
  122.     /**
  123.      * @throws BadRequestHttpException
  124.      */
  125.     private function applyMapToVariables(array $map, array $variables, array $files): array
  126.     {
  127.         foreach ($map as $key => $value) {
  128.             if (null === $file $files[$key] ?? null) {
  129.                 throw new BadRequestHttpException('GraphQL multipart request file has not been sent correctly.');
  130.             }
  131.             foreach ($value as $mapValue) {
  132.                 $path explode('.', (string) $mapValue);
  133.                 if ('variables' !== $path[0]) {
  134.                     throw new BadRequestHttpException('GraphQL multipart request path in map is invalid.');
  135.                 }
  136.                 unset($path[0]);
  137.                 $mapPathExistsInVariables array_reduce($path, static fn (array $inVariablesstring $pathElement) => \array_key_exists($pathElement$inVariables) ? $inVariables[$pathElement] : false$variables);
  138.                 if (false === $mapPathExistsInVariables) {
  139.                     throw new BadRequestHttpException('GraphQL multipart request path in map does not match the variables.');
  140.                 }
  141.                 $variableFileValue = &$variables;
  142.                 foreach ($path as $pathValue) {
  143.                     $variableFileValue = &$variableFileValue[$pathValue];
  144.                 }
  145.                 $variableFileValue $file;
  146.             }
  147.         }
  148.         return $variables;
  149.     }
  150.     /**
  151.      * @throws BadRequestHttpException
  152.      */
  153.     private function decodeVariables(string $variables): array
  154.     {
  155.         if (!\is_array($decoded json_decode($variablestrue512\JSON_ERROR_NONE))) {
  156.             throw new BadRequestHttpException('GraphQL variables are not valid JSON.');
  157.         }
  158.         return $decoded;
  159.     }
  160. }