Passed
Push — master ( 6859a9...73a630 )
by Adrien
11:45
created

UploadMiddleware::parseUploadedFiles()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 7

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 7
eloc 18
c 3
b 0
f 0
nc 8
nop 1
dl 0
loc 32
ccs 18
cts 18
cp 1
crap 7
rs 8.8333
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Upload;
6
7
use GraphQL\Error\InvariantViolation;
8
use GraphQL\Server\RequestError;
9
use GraphQL\Utils\Utils;
10
use Psr\Http\Message\ResponseInterface;
11
use Psr\Http\Message\ServerRequestInterface;
12
use Psr\Http\Server\MiddlewareInterface;
13
use Psr\Http\Server\RequestHandlerInterface;
14
15
class UploadMiddleware implements MiddlewareInterface
16
{
17 1
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
18
    {
19 1
        $request = $this->processRequest($request);
20
21 1
        return $handler->handle($request);
22
    }
23
24
    /**
25
     * Process the request and return either a modified request or the original one.
26
     */
27 12
    public function processRequest(ServerRequestInterface $request): ServerRequestInterface
28
    {
29 12
        $contentType = $request->getHeader('content-type')[0] ?? '';
30
31 12
        if (mb_stripos($contentType, 'multipart/form-data') !== false) {
32 10
            $this->validateParsedBody($request);
33 7
            $request = $this->parseUploadedFiles($request);
34
        }
35
36 5
        return $request;
37
    }
38
39
    /**
40
     * Inject uploaded files defined in the 'map' key into the 'variables' key.
41
     */
42 7
    private function parseUploadedFiles(ServerRequestInterface $request): ServerRequestInterface
43
    {
44
        /** @var string[] $bodyParams */
45 7
        $bodyParams = $request->getParsedBody();
46
47 7
        $map = $this->decodeArray($bodyParams, 'map');
48 4
        $result = $this->decodeArray($bodyParams, 'operations');
49
50 4
        $uploadedFiles = $request->getUploadedFiles();
51 4
        foreach ($map as $fileKey => $locations) {
52 3
            foreach ($locations as $location) {
53 3
                $items = &$result;
54 3
                foreach (explode('.', $location) as $key) {
55 3
                    if (!isset($items[$key]) || !is_array($items[$key])) {
56 3
                        $items[$key] = [];
57
                    }
58 3
                    $items = &$items[$key];
59
                }
60
61 3
                if (!array_key_exists($fileKey, $uploadedFiles)) {
62 1
                    throw new RequestError(
63 1
                        "GraphQL query declared an upload in `$location`, but no corresponding file were actually uploaded"
64
                    );
65
                }
66
67 3
                $items = $uploadedFiles[$fileKey];
68
            }
69
        }
70
71
        return $request
72 3
            ->withHeader('content-type', 'application/json')
73 3
            ->withParsedBody($result);
74
    }
75
76
    /**
77
     * Validates that the request meet our expectations.
78
     */
79 10
    private function validateParsedBody(ServerRequestInterface $request): void
80
    {
81 10
        $bodyParams = $request->getParsedBody();
82
83 10
        if (null === $bodyParams) {
84 1
            throw new InvariantViolation(
85
                'PSR-7 request is expected to provide parsed body for "multipart/form-data" requests but got null'
86
            );
87
        }
88
89 9
        if (!is_array($bodyParams)) {
90 1
            throw new RequestError(
91 1
                'GraphQL Server expects JSON object or array, but got ' . Utils::printSafeJson($bodyParams)
92
            );
93
        }
94
95 8
        if (empty($bodyParams)) {
96 1
            throw new InvariantViolation(
97
                'PSR-7 request is expected to provide parsed body for "multipart/form-data" requests but got empty array'
98
            );
99
        }
100
    }
101
102
    /**
103
     * @param string[] $bodyParams
104
     *
105
     * @return string[][]
106
     */
107 7
    private function decodeArray(array $bodyParams, string $key): array
108
    {
109 7
        if (!isset($bodyParams[$key])) {
110 1
            throw new RequestError("The request must define a `$key`");
111
        }
112
113 6
        $value = json_decode($bodyParams[$key], true);
114 6
        if (!is_array($value)) {
115 2
            throw new RequestError("The `$key` key must be a JSON encoded array");
116
        }
117
118 4
        return $value;
119
    }
120
}
121