• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 10593706487

28 Aug 2024 09:10AM UTC coverage: 7.69% (-0.01%) from 7.704%
10593706487

push

github

web-flow
feat(laravel): enable graphQl support (#6550)

0 of 318 new or added lines in 12 files covered. (0.0%)

4066 existing lines in 144 files now uncovered.

12488 of 162383 relevant lines covered (7.69%)

22.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/Laravel/GraphQl/Controller/EntrypointController.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
declare(strict_types=1);
13

14
namespace ApiPlatform\Laravel\GraphQl\Controller;
15

16
use ApiPlatform\GraphQl\Error\ErrorHandlerInterface;
17
use ApiPlatform\GraphQl\ExecutorInterface;
18
use ApiPlatform\GraphQl\Type\SchemaBuilderInterface;
19
use ApiPlatform\Metadata\Util\ContentNegotiationTrait;
20
use GraphQL\Error\DebugFlag;
21
use GraphQL\Error\Error;
22
use GraphQL\Executor\ExecutionResult;
23
use Illuminate\Http\Request;
24
use Negotiation\Negotiator;
25
use Symfony\Component\HttpFoundation\JsonResponse;
26
use Symfony\Component\HttpFoundation\Response;
27
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
28
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
29

30
final class EntrypointController
31
{
32
    use ContentNegotiationTrait;
33
    private int $debug;
34

35
    public function __construct(
36
        private readonly SchemaBuilderInterface $schemaBuilder,
37
        private readonly ExecutorInterface $executor,
38
        private readonly GraphiQlController $graphiQlAction,
39
        private readonly NormalizerInterface $normalizer,
40
        private readonly ErrorHandlerInterface $errorHandler,
41
        bool $debug = false,
42
        ?Negotiator $negotiator = null
43
    ) {
NEW
44
        $this->debug = $debug ? DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE : DebugFlag::NONE;
×
NEW
45
        $this->negotiator = $negotiator ?? new Negotiator();
×
46
    }
47

48
    public function __invoke(Request $request): Response
49
    {
NEW
50
        $formats = ['json' => ['application/json'], 'html' => ['text/html']];
×
NEW
51
        $format = $this->getRequestFormat($request, $formats, false);
×
52

53
        try {
NEW
54
            if ($request->isMethod('GET') && 'html' === $format) {
×
NEW
55
                return ($this->graphiQlAction)();
×
56
            }
57

NEW
58
            [$query, $operationName, $variables] = $this->parseRequest($request);
×
NEW
59
            if (null === $query) {
×
NEW
60
                throw new BadRequestHttpException('GraphQL query is not valid.');
×
61
            }
62

NEW
63
            $executionResult = $this->executor
×
NEW
64
                ->executeQuery($this->schemaBuilder->getSchema(), $query, null, null, $variables, $operationName)
×
NEW
65
                ->setErrorsHandler($this->errorHandler)
×
NEW
66
                ->setErrorFormatter($this->normalizer->normalize(...));
×
NEW
67
        } catch (\Exception $exception) {
×
NEW
68
            $executionResult = (new ExecutionResult(null, [new Error($exception->getMessage(), null, null, [], null, $exception)]))
×
NEW
69
                ->setErrorsHandler($this->errorHandler)
×
NEW
70
                ->setErrorFormatter($this->normalizer->normalize(...));
×
71
        }
72

NEW
73
        return new JsonResponse($executionResult->toArray($this->debug));
×
74
    }
75

76
    /**
77
     * @throws BadRequestHttpException
78
     *
79
     * @return array{0: array<string, mixed>|null, 1: string, 2: array<string, mixed>}
80
     */
81
    private function parseRequest(Request $request): array
82
    {
NEW
83
        $queryParameters = $request->query->all();
×
NEW
84
        $query = $queryParameters['query'] ?? null;
×
NEW
85
        $operationName = $queryParameters['operationName'] ?? null;
×
NEW
86
        if ($variables = $queryParameters['variables'] ?? []) {
×
NEW
87
            $variables = $this->decodeVariables($variables);
×
88
        }
89

NEW
90
        if (!$request->isMethod('POST')) {
×
NEW
91
            return [$query, $operationName, $variables];
×
92
        }
93

NEW
94
        $contentType = method_exists(Request::class, 'getContentTypeFormat') ? $request->getContentTypeFormat() : $request->getContentType();
×
NEW
95
        if ('json' === $contentType) {
×
NEW
96
            return $this->parseData($query, $operationName, $variables, $request->getContent());
×
97
        }
98

NEW
99
        if ('graphql' === $contentType) {
×
NEW
100
            $query = $request->getContent();
×
101
        }
102

NEW
103
        if (\in_array($contentType, ['multipart', 'form'], true)) {
×
NEW
104
            return $this->parseMultipartRequest($query, $operationName, $variables, $request->request->all(), $request->files->all());
×
105
        }
106

NEW
107
        return [$query, $operationName, $variables];
×
108
    }
109

110
    /**
111
     * @param array<string,mixed> $variables
112
     *
113
     * @throws BadRequestHttpException
114
     *
115
     * @return array{0: array<string, mixed>, 1: string, 2: array<string, mixed>}
116
     */
117
    private function parseData(?string $query, ?string $operationName, array $variables, string $jsonContent): array
118
    {
NEW
119
        if (!\is_array($data = json_decode($jsonContent, true, 512, \JSON_ERROR_NONE))) {
×
NEW
120
            throw new BadRequestHttpException('GraphQL data is not valid JSON.');
×
121
        }
122

NEW
123
        if (isset($data['query'])) {
×
NEW
124
            $query = $data['query'];
×
125
        }
126

NEW
127
        if (isset($data['variables'])) {
×
NEW
128
            $variables = \is_array($data['variables']) ? $data['variables'] : $this->decodeVariables($data['variables']);
×
129
        }
130

NEW
131
        if (isset($data['operationName'])) {
×
NEW
132
            $operationName = $data['operationName'];
×
133
        }
134

NEW
135
        return [$query, $operationName, $variables];
×
136
    }
137

138
    /**
139
     * @param array<string,mixed> $variables
140
     * @param array<string,mixed> $bodyParameters
141
     * @param array<string,mixed> $files
142
     *
143
     * @throws BadRequestHttpException
144
     *
145
     * @return array{0: array<string, mixed>, 1: string, 2: array<string, mixed>}
146
     */
147
    private function parseMultipartRequest(?string $query, ?string $operationName, array $variables, array $bodyParameters, array $files): array
148
    {
NEW
149
        if ((null === $operations = $bodyParameters['operations'] ?? null) || (null === $map = $bodyParameters['map'] ?? null)) {
×
NEW
150
            throw new BadRequestHttpException('GraphQL multipart request does not respect the specification.');
×
151
        }
152

NEW
153
        [$query, $operationName, $variables] = $this->parseData($query, $operationName, $variables, $operations);
×
154

155
        /** @var string $map */
NEW
156
        if (!\is_array($decodedMap = json_decode($map, true, 512, \JSON_ERROR_NONE))) {
×
NEW
157
            throw new BadRequestHttpException('GraphQL multipart request map is not valid JSON.');
×
158
        }
159

NEW
160
        $variables = $this->applyMapToVariables($decodedMap, $variables, $files);
×
161

NEW
162
        return [$query, $operationName, $variables];
×
163
    }
164

165
    /**
166
     * @param array<string,mixed> $map
167
     * @param array<string,mixed> $variables
168
     * @param array<string,mixed> $files
169
     *
170
     * @throws BadRequestHttpException
171
     */
172
    private function applyMapToVariables(array $map, array $variables, array $files): array
173
    {
NEW
174
        foreach ($map as $key => $value) {
×
NEW
175
            if (null === $file = $files[$key] ?? null) {
×
NEW
176
                throw new BadRequestHttpException('GraphQL multipart request file has not been sent correctly.');
×
177
            }
178

NEW
179
            foreach ($value as $mapValue) {
×
NEW
180
                $path = explode('.', (string) $mapValue);
×
181

NEW
182
                if ('variables' !== $path[0]) {
×
NEW
183
                    throw new BadRequestHttpException('GraphQL multipart request path in map is invalid.');
×
184
                }
185

NEW
186
                unset($path[0]);
×
187

NEW
188
                $mapPathExistsInVariables = array_reduce($path, static fn (array $inVariables, string $pathElement) => \array_key_exists($pathElement, $inVariables) ? $inVariables[$pathElement] : false, $variables);
×
189

NEW
190
                if (false === $mapPathExistsInVariables) {
×
NEW
191
                    throw new BadRequestHttpException('GraphQL multipart request path in map does not match the variables.');
×
192
                }
193

NEW
194
                $variableFileValue = &$variables;
×
NEW
195
                foreach ($path as $pathValue) {
×
NEW
196
                    $variableFileValue = &$variableFileValue[$pathValue];
×
197
                }
NEW
198
                $variableFileValue = $file;
×
199
            }
200
        }
201

NEW
202
        return $variables;
×
203
    }
204

205
    /**
206
     * @throws BadRequestHttpException
207
     *
208
     * @return array<string, mixed>
209
     */
210
    private function decodeVariables(string $variables): array
211
    {
NEW
212
        if (!\is_array($decoded = json_decode($variables, true, 512, \JSON_ERROR_NONE))) {
×
NEW
213
            throw new BadRequestHttpException('GraphQL variables are not valid JSON.');
×
214
        }
215

NEW
216
        return $decoded;
×
217
    }
218
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc