Completed
Push — master ( 08ed0c...2fd031 )
by Tobias
01:24
created

ServerRequestCreator::normalizeNestedFileSpec()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 10
cts 10
cp 1
rs 9.7
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Nyholm\Psr7Server;
6
7
use Interop\Http\Factory\StreamFactoryInterface;
8
use Interop\Http\Factory\UploadedFileFactoryInterface;
9
use Interop\Http\Factory\UriFactoryInterface;
10
use Psr\Http\Message\ServerRequestInterface;
11
use Psr\Http\Message\UploadedFileInterface;
12
use Interop\Http\Factory\ServerRequestFactoryInterface;
13
use Psr\Http\Message\UriInterface;
14
15
class ServerRequestCreator
16
{
17
    private $serverRequestFactory;
18
19
    private $uriFactory;
20
21
    private $uploadedFileFactory;
22
23
    private $streamFactory;
24
25 13
    public function __construct(
26
        ServerRequestFactoryInterface $serverRequestFactory,
27
        UriFactoryInterface $uriFactory,
28
        UploadedFileFactoryInterface $uploadedFileFactory,
29
        StreamFactoryInterface $streamFactory
30
    ) {
31 13
        $this->serverRequestFactory = $serverRequestFactory;
32 13
        $this->uriFactory = $uriFactory;
33 13
        $this->uploadedFileFactory = $uploadedFileFactory;
34 13
        $this->streamFactory = $streamFactory;
35 13
    }
36
37
    /**
38
     * Create a new server request from the current environment variables.
39
     * Defaults to a GET request to minimise the risk of an \InvalidArgumentException.
40
     * Includes the current request headers as supplied by the server through `getallheaders()`.
41
     *
42
     * @throws \InvalidArgumentException If no valid method or URI can be determined.
43
     */
44
    public function fromGlobals(): ServerRequestInterface
45
    {
46
        $server = $_SERVER;
47
        if (false === isset($server['REQUEST_METHOD'])) {
48
            $server['REQUEST_METHOD'] = 'GET';
49
        }
50
        $headers = function_exists('getallheaders') ? getallheaders() : [];
51
52
        return $this->fromArrays($server, $headers, $_COOKIE, $_GET, $_POST, $_FILES);
53
    }
54
55
    /**
56
     * Create a new server request from a set of arrays.
57
     *
58
     * @param array $server  Typically $_SERVER or similar structure.
59
     * @param array $headers Typically the output of getallheaders() or similar structure.
60
     * @param array $cookie  Typically $_COOKIE or similar structure.
61
     * @param array $get     Typically $_GET or similar structure.
62
     * @param array $post    Typically $_POST or similar structure.
63
     * @param array $files   Typically $_FILES or similar structure.
64
     *
65
     * @throws \InvalidArgumentException If no valid method or URI can be determined.
66
     */
67 7
    public function fromArrays(array $server, array $headers = [], array $cookie = [], array $get = [], array $post = [], array $files = []): ServerRequestInterface
68
    {
69 7
        $method = $this->getMethodFromEnv($server);
70 7
        $uri = $this->getUriFromEnvWithHTTP($server);
71 7
        $protocol = isset($server['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $server['SERVER_PROTOCOL']) : '1.1';
72
73 7
        $serverRequest = $this->serverRequestFactory->createServerRequest($method, $uri, $server);
74 7
        foreach ($headers as $name => $value) {
75
            $serverRequest = $serverRequest->withAddedHeader($name, $value);
76
        }
77
78
        return $serverRequest
79 7
            ->withProtocolVersion($protocol)
80 7
            ->withCookieParams($cookie)
81 7
            ->withQueryParams($get)
82 7
            ->withParsedBody($post)
83 7
            ->withUploadedFiles($this->normalizeFiles($files));
84
    }
85
86 7
    private function getMethodFromEnv(array $environment): string
87
    {
88 7
        if (false === isset($environment['REQUEST_METHOD'])) {
89
            throw new \InvalidArgumentException('Cannot determine HTTP method');
90
        }
91
92 7
        return $environment['REQUEST_METHOD'];
93
    }
94
95 7
    private function getUriFromEnvWithHTTP(array $environment): UriInterface
96
    {
97 7
        $uri = $this->createUriFromArray($environment);
98 7
        if (empty($uri->getScheme())) {
99 6
            $uri = $uri->withScheme('http');
100
        }
101
102 7
        return $uri;
103
    }
104
105
    /**
106
     * Return an UploadedFile instance array.
107
     *
108
     * @param array $files A array which respect $_FILES structure
109
     *
110
     * @return UploadedFileInterface[]
111
     *
112
     * @throws \InvalidArgumentException for unrecognized values
113
     */
114 7
    private function normalizeFiles(array $files): array
115
    {
116 7
        $normalized = [];
117
118 7
        foreach ($files as $key => $value) {
119 7
            if ($value instanceof UploadedFileInterface) {
120 2
                $normalized[$key] = $value;
121 6
            } elseif (is_array($value) && isset($value['tmp_name'])) {
122 4
                $normalized[$key] = $this->createUploadedFileFromSpec($value);
123 2
            } elseif (is_array($value)) {
124 1
                $normalized[$key] = $this->normalizeFiles($value);
125
            } else {
126 7
                throw new \InvalidArgumentException('Invalid value in files specification');
127
            }
128
        }
129
130 6
        return $normalized;
131
    }
132
133
    /**
134
     * Create and return an UploadedFile instance from a $_FILES specification.
135
     *
136
     * If the specification represents an array of values, this method will
137
     * delegate to normalizeNestedFileSpec() and return that return value.
138
     *
139
     * @param array $value $_FILES struct
140
     *
141
     * @return array|UploadedFileInterface
142
     */
143 4
    private function createUploadedFileFromSpec(array $value)
144
    {
145 4
        if (is_array($value['tmp_name'])) {
146 1
            return $this->normalizeNestedFileSpec($value);
147
        }
148
149 4
        return $this->uploadedFileFactory->createUploadedFile(
150 4
            $this->streamFactory->createStreamFromFile($value['tmp_name']),
151 4
            (int) $value['size'],
152 4
            (int) $value['error'],
153 4
            $value['name'],
154 4
            $value['type']
155
        );
156
    }
157
158
    /**
159
     * Normalize an array of file specifications.
160
     *
161
     * Loops through all nested files and returns a normalized array of
162
     * UploadedFileInterface instances.
163
     *
164
     * @param array $files
165
     *
166
     * @return UploadedFileInterface[]
167
     */
168 1
    private function normalizeNestedFileSpec(array $files = []): array
169
    {
170 1
        $normalizedFiles = [];
171
172 1
        foreach (array_keys($files['tmp_name']) as $key) {
173
            $spec = [
174 1
                'tmp_name' => $files['tmp_name'][$key],
175 1
                'size' => $files['size'][$key],
176 1
                'error' => $files['error'][$key],
177 1
                'name' => $files['name'][$key],
178 1
                'type' => $files['type'][$key],
179
            ];
180 1
            $normalizedFiles[$key] = $this->createUploadedFileFromSpec($spec);
181
        }
182
183 1
        return $normalizedFiles;
184
    }
185
186
    /**
187
     * Create a new uri from server variable.
188
     *
189
     * @param array $server Typically $_SERVER or similar structure.
190
     */
191 13
    private function createUriFromArray(array $server): UriInterface
192
    {
193 13
        $uri = $this->uriFactory->createUri('');
194
195 13
        if (isset($server['REQUEST_SCHEME'])) {
196
            $uri = $uri->withScheme($server['REQUEST_SCHEME']);
197 13
        } elseif (isset($server['HTTPS'])) {
198 6
            $uri = $uri->withScheme('on' === $server['HTTPS'] ? 'https' : 'http');
199
        }
200
201 13
        if (isset($server['HTTP_HOST'])) {
202 5
            $uri = $uri->withHost($server['HTTP_HOST']);
203 8
        } elseif (isset($server['SERVER_NAME'])) {
204 1
            $uri = $uri->withHost($server['SERVER_NAME']);
205
        }
206
207 13
        if (isset($server['SERVER_PORT'])) {
208 6
            $uri = $uri->withPort($server['SERVER_PORT']);
209
        }
210
211 13
        if (isset($server['REQUEST_URI'])) {
212 6
            $uri = $uri->withPath(current(explode('?', $server['REQUEST_URI'])));
213
        }
214
215 13
        if (isset($server['QUERY_STRING'])) {
216 6
            $uri = $uri->withQuery($server['QUERY_STRING']);
217
        }
218
219 13
        return $uri;
220
    }
221
}
222