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

api-platform / core / 11182119487

04 Oct 2024 02:50PM UTC coverage: 7.837% (+0.4%) from 7.441%
11182119487

push

github

soyuka
Merge 4.0

0 of 266 new or added lines in 25 files covered. (0.0%)

9900 existing lines in 360 files now uncovered.

12939 of 165109 relevant lines covered (7.84%)

27.02 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
    /**
36
     * @param array<string, string[]> $formats
37
     */
38
    public function __construct(
39
        private readonly SchemaBuilderInterface $schemaBuilder,
40
        private readonly ExecutorInterface $executor,
41
        private readonly GraphiQlController $graphiQlAction,
42
        private readonly NormalizerInterface $normalizer,
43
        private readonly ErrorHandlerInterface $errorHandler,
44
        bool $debug = false,
45
        ?Negotiator $negotiator = null,
46
        private readonly array $formats = [],
47
    ) {
48
        $this->debug = $debug ? DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE : DebugFlag::NONE;
×
49
        $this->negotiator = $negotiator ?? new Negotiator();
×
50
    }
51

52
    public function __invoke(Request $request): Response
53
    {
54
        $formats = ['json' => ['application/json'], 'html' => ['text/html']];
×
55

NEW
56
        foreach ($this->formats as $k => $f) {
×
NEW
57
            if (!isset($formats[$k])) {
×
NEW
58
                $formats[$k] = $f;
×
59
            }
60
        }
61

NEW
62
        $this->addRequestFormats($request, $formats);
×
UNCOV
63
        $format = $this->getRequestFormat($request, $formats, false);
×
NEW
64
        $request->setRequestFormat($format);
×
65

66
        try {
67
            if ($request->isMethod('GET') && 'html' === $format) {
×
68
                return ($this->graphiQlAction)();
×
69
            }
70

NEW
71
            [$query, $operationName, $variables] = $this->parseRequest($request, $format);
×
72
            if (null === $query) {
×
73
                throw new BadRequestHttpException('GraphQL query is not valid.');
×
74
            }
75

76
            $executionResult = $this->executor
×
77
                ->executeQuery($this->schemaBuilder->getSchema(), $query, null, null, $variables, $operationName)
×
78
                ->setErrorsHandler($this->errorHandler)
×
79
                ->setErrorFormatter($this->normalizer->normalize(...));
×
80
        } catch (\Exception $exception) {
×
81
            $executionResult = (new ExecutionResult(null, [new Error($exception->getMessage(), null, null, [], null, $exception)]))
×
82
                ->setErrorsHandler($this->errorHandler)
×
83
                ->setErrorFormatter($this->normalizer->normalize(...));
×
84
        }
85

86
        return new JsonResponse($executionResult->toArray($this->debug));
×
87
    }
88

89
    /**
90
     * @throws BadRequestHttpException
91
     *
92
     * @return array{0: array<string, mixed>|null, 1: string, 2: array<string, mixed>}
93
     */
94
    private function parseRequest(Request $request, string $format): array
95
    {
96
        $queryParameters = $request->query->all();
×
97
        $query = $queryParameters['query'] ?? null;
×
98
        $operationName = $queryParameters['operationName'] ?? null;
×
99
        if ($variables = $queryParameters['variables'] ?? []) {
×
100
            $variables = $this->decodeVariables($variables);
×
101
        }
102

103
        if (!$request->isMethod('POST')) {
×
104
            return [$query, $operationName, $variables];
×
105
        }
106

NEW
107
        if ('json' === $format) {
×
108
            return $this->parseData($query, $operationName, $variables, $request->getContent());
×
109
        }
110

NEW
111
        if ('graphql' === $format) {
×
112
            $query = $request->getContent();
×
113
        }
114

NEW
115
        if ('multipart' === $format) {
×
116
            return $this->parseMultipartRequest($query, $operationName, $variables, $request->request->all(), $request->files->all());
×
117
        }
118

119
        return [$query, $operationName, $variables];
×
120
    }
121

122
    /**
123
     * @param array<string,mixed> $variables
124
     *
125
     * @throws BadRequestHttpException
126
     *
127
     * @return array{0: array<string, mixed>, 1: string, 2: array<string, mixed>}
128
     */
129
    private function parseData(?string $query, ?string $operationName, array $variables, string $jsonContent): array
130
    {
131
        if (!\is_array($data = json_decode($jsonContent, true, 512, \JSON_ERROR_NONE))) {
×
132
            throw new BadRequestHttpException('GraphQL data is not valid JSON.');
×
133
        }
134

135
        if (isset($data['query'])) {
×
136
            $query = $data['query'];
×
137
        }
138

139
        if (isset($data['variables'])) {
×
140
            $variables = \is_array($data['variables']) ? $data['variables'] : $this->decodeVariables($data['variables']);
×
141
        }
142

143
        if (isset($data['operationName'])) {
×
144
            $operationName = $data['operationName'];
×
145
        }
146

147
        return [$query, $operationName, $variables];
×
148
    }
149

150
    /**
151
     * @param array<string,mixed> $variables
152
     * @param array<string,mixed> $bodyParameters
153
     * @param array<string,mixed> $files
154
     *
155
     * @throws BadRequestHttpException
156
     *
157
     * @return array{0: array<string, mixed>, 1: string, 2: array<string, mixed>}
158
     */
159
    private function parseMultipartRequest(?string $query, ?string $operationName, array $variables, array $bodyParameters, array $files): array
160
    {
161
        if ((null === $operations = $bodyParameters['operations'] ?? null) || (null === $map = $bodyParameters['map'] ?? null)) {
×
162
            throw new BadRequestHttpException('GraphQL multipart request does not respect the specification.');
×
163
        }
164

165
        [$query, $operationName, $variables] = $this->parseData($query, $operationName, $variables, $operations);
×
166

167
        /** @var string $map */
168
        if (!\is_array($decodedMap = json_decode($map, true, 512, \JSON_ERROR_NONE))) {
×
169
            throw new BadRequestHttpException('GraphQL multipart request map is not valid JSON.');
×
170
        }
171

172
        $variables = $this->applyMapToVariables($decodedMap, $variables, $files);
×
173

174
        return [$query, $operationName, $variables];
×
175
    }
176

177
    /**
178
     * @param array<string,mixed> $map
179
     * @param array<string,mixed> $variables
180
     * @param array<string,mixed> $files
181
     *
182
     * @throws BadRequestHttpException
183
     */
184
    private function applyMapToVariables(array $map, array $variables, array $files): array
185
    {
186
        foreach ($map as $key => $value) {
×
187
            if (null === $file = $files[$key] ?? null) {
×
188
                throw new BadRequestHttpException('GraphQL multipart request file has not been sent correctly.');
×
189
            }
190

191
            foreach ($value as $mapValue) {
×
192
                $path = explode('.', (string) $mapValue);
×
193

194
                if ('variables' !== $path[0]) {
×
195
                    throw new BadRequestHttpException('GraphQL multipart request path in map is invalid.');
×
196
                }
197

198
                unset($path[0]);
×
199

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

202
                if (false === $mapPathExistsInVariables) {
×
203
                    throw new BadRequestHttpException('GraphQL multipart request path in map does not match the variables.');
×
204
                }
205

206
                $variableFileValue = &$variables;
×
207
                foreach ($path as $pathValue) {
×
208
                    $variableFileValue = &$variableFileValue[$pathValue];
×
209
                }
210
                $variableFileValue = $file;
×
211
            }
212
        }
213

214
        return $variables;
×
215
    }
216

217
    /**
218
     * @throws BadRequestHttpException
219
     *
220
     * @return array<string, mixed>
221
     */
222
    private function decodeVariables(string $variables): array
223
    {
224
        if (!\is_array($decoded = json_decode($variables, true, 512, \JSON_ERROR_NONE))) {
×
225
            throw new BadRequestHttpException('GraphQL variables are not valid JSON.');
×
226
        }
227

228
        return $decoded;
×
229
    }
230
}
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