Passed
Pull Request — master (#4)
by Evgeniy
12:51 queued 10:40
created

ServerRequestFactory::getHeadersFromGlobals()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 34
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 8.125

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 8
eloc 15
c 2
b 0
f 0
nc 10
nop 0
dl 0
loc 34
ccs 14
cts 16
cp 0.875
crap 8.125
rs 8.4444
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Runner\Http;
6
7
use InvalidArgumentException;
8
use Psr\Http\Message\ServerRequestFactoryInterface;
9
use Psr\Http\Message\ServerRequestInterface;
10
use Psr\Http\Message\StreamFactoryInterface;
11
use Psr\Http\Message\StreamInterface;
12
use Psr\Http\Message\UploadedFileFactoryInterface;
13
use Psr\Http\Message\UriFactoryInterface;
14
use Psr\Http\Message\UriInterface;
15
use RuntimeException;
16
17
use function array_key_exists;
18
use function explode;
19
use function fopen;
20
use function function_exists;
21
use function getallheaders;
22
use function is_array;
23
use function is_resource;
24
use function is_string;
25
use function preg_match;
26
use function str_replace;
27
use function strncmp;
28
use function strtolower;
29
use function substr;
30
use function ucwords;
31
32
final class ServerRequestFactory
33
{
34
    private ServerRequestFactoryInterface $serverRequestFactory;
35
    private UriFactoryInterface $uriFactory;
36
    private UploadedFileFactoryInterface $uploadedFileFactory;
37
    private StreamFactoryInterface $streamFactory;
38
39 50
    public function __construct(
40
        ServerRequestFactoryInterface $serverRequestFactory,
41
        UriFactoryInterface $uriFactory,
42
        UploadedFileFactoryInterface $uploadedFileFactory,
43
        StreamFactoryInterface $streamFactory
44
    ) {
45 50
        $this->serverRequestFactory = $serverRequestFactory;
46 50
        $this->uriFactory = $uriFactory;
47 50
        $this->uploadedFileFactory = $uploadedFileFactory;
48 50
        $this->streamFactory = $streamFactory;
49 50
    }
50
51 22
    public function createFromGlobals(): ServerRequestInterface
52
    {
53
        /** @psalm-var array<string, string> $_SERVER */
54 22
        return $this->createFromParameters(
55
            $_SERVER,
56 22
            $this->getHeadersFromGlobals(),
57
            $_COOKIE,
58
            $_GET,
59
            $_POST,
60
            $_FILES,
61 22
            fopen('php://input', 'rb') ?: null
62
        );
63
    }
64
65
    /**
66
     * @param array $server
67
     * @param array $headers
68
     * @param array $cookies
69
     * @param array $get
70
     * @param array $post
71
     * @param array $files
72
     * @param resource|StreamInterface|string|null $body
73
     *
74
     * @psalm-param array<string, string> $server
75
     * @psalm-param array<string, string|string[]> $headers
76
     * @psalm-param mixed $body
77
     *
78
     * @return ServerRequestInterface
79
     */
80 50
    public function createFromParameters(
81
        array $server,
82
        array $headers = [],
83
        array $cookies = [],
84
        array $get = [],
85
        array $post = [],
86
        array $files = [],
87
        mixed $body = null
88
    ): ServerRequestInterface {
89 50
        $method = $server['REQUEST_METHOD'] ?? null;
90
91 50
        if ($method === null) {
92 1
            throw new RuntimeException('Unable to determine HTTP request method.');
93
        }
94
95 49
        $uri = $this->getUri($server);
96 49
        $request = $this->serverRequestFactory->createServerRequest($method, $uri, $server);
97
98 49
        foreach ($headers as $name => $value) {
99 13
            if ($name === 'Host' && $request->hasHeader('Host')) {
100 13
                continue;
101
            }
102
103 5
            $request = $request->withAddedHeader($name, $value);
104
        }
105
106 49
        $protocol = '1.1';
107 49
        if (array_key_exists('SERVER_PROTOCOL', $server) && $server['SERVER_PROTOCOL'] !== '') {
108 2
            $protocol = str_replace('HTTP/', '', $server['SERVER_PROTOCOL']);
109
        }
110
111 49
        $request = $request
112 49
            ->withProtocolVersion($protocol)
113 49
            ->withQueryParams($get)
114 49
            ->withParsedBody($post)
115 49
            ->withCookieParams($cookies)
116 49
            ->withUploadedFiles($this->getUploadedFilesArray($files))
117
        ;
118
119 49
        if ($body === null) {
120 17
            return $request;
121
        }
122
123 32
        if ($body instanceof StreamInterface) {
124 1
            return $request->withBody($body);
125
        }
126
127 31
        if (is_string($body)) {
128 1
            return $request->withBody($this->streamFactory->createStream($body));
129
        }
130
131 30
        if (is_resource($body)) {
132 23
            return $request->withBody($this->streamFactory->createStreamFromResource($body));
133
        }
134
135 7
        throw new InvalidArgumentException(
136
            'Body parameter for "ServerRequestFactory::createFromParameters()"'
137 7
            . 'must be instance of StreamInterface, resource or null.',
138
        );
139
    }
140
141
    /**
142
     * @psalm-param array<string, string> $server
143
     */
144 49
    private function getUri(array $server): UriInterface
145
    {
146 49
        $uri = $this->uriFactory->createUri();
147
148 49
        if (array_key_exists('HTTPS', $server) && $server['HTTPS'] !== '' && $server['HTTPS'] !== 'off') {
149 4
            $uri = $uri->withScheme('https');
150
        } else {
151 45
            $uri = $uri->withScheme('http');
152
        }
153
154 49
        if (isset($server['HTTP_HOST'])) {
155 20
            $uri = preg_match('/^(.+):(\d+)$/', $server['HTTP_HOST'], $matches) === 1
156 6
                ? $uri->withHost($matches[1])->withPort((int) $matches[2])
157 14
                : $uri->withHost($server['HTTP_HOST'])
158
            ;
159 29
        } elseif (isset($server['SERVER_NAME'])) {
160 2
            $uri = $uri->withHost($server['SERVER_NAME']);
161
        }
162
163 49
        if (isset($server['SERVER_PORT'])) {
164 2
            $uri = $uri->withPort((int) $server['SERVER_PORT']);
165
        }
166
167 49
        if (isset($server['REQUEST_URI'])) {
168 2
            $uri = $uri->withPath(explode('?', $server['REQUEST_URI'])[0]);
169
        }
170
171 49
        if (isset($server['QUERY_STRING'])) {
172 2
            $uri = $uri->withQuery($server['QUERY_STRING']);
173
        }
174
175 49
        return $uri;
176
    }
177
178
    /**
179
     * @psalm-return array<string, string>
180
     */
181 22
    private function getHeadersFromGlobals(): array
182
    {
183 22
        if (function_exists('getallheaders')) {
184
            /** @psalm-var array<string, string>|false $headers */
185
            $headers = getallheaders();
186
            return $headers === false ? [] : $headers;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $headers === false ? array() : $headers could return the type true which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
187
        }
188
189 22
        $headers = [];
190
191
        /**
192
         * @var string $name
193
         * @var string $value
194
         */
195 22
        foreach ($_SERVER as $name => $value) {
196 22
            if (strncmp($name, 'REDIRECT_', 9) === 0) {
197 5
                $name = substr($name, 9);
198
199 5
                if (array_key_exists($name, $_SERVER)) {
200 5
                    continue;
201
                }
202
            }
203
204 22
            if (strncmp($name, 'HTTP_', 5) === 0) {
205 13
                $headers[$this->normalizeHeaderName(substr($name, 5))] = $value;
206 13
                continue;
207
            }
208
209 22
            if (strncmp($name, 'CONTENT_', 8) === 0) {
210 5
                $headers[$this->normalizeHeaderName($name)] = $value;
211
            }
212
        }
213
214 22
        return $headers;
215
    }
216
217 13
    private function normalizeHeaderName(string $name): string
218
    {
219 13
        return str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $name))));
220
    }
221
222 49
    private function getUploadedFilesArray(array $filesArray): array
223
    {
224 49
        $files = [];
225
226
        /** @var array $info */
227 49
        foreach ($filesArray as $class => $info) {
228 6
            $files[$class] = [];
229 6
            $this->populateUploadedFileRecursive(
230 6
                $files[$class],
231 6
                $info['name'],
232 6
                $info['tmp_name'],
233 6
                $info['type'],
234 6
                $info['size'],
235 6
                $info['error'],
236
            );
237
        }
238
239 49
        return $files;
240
    }
241
242
    /**
243
     * Populates uploaded files array from $_FILE data structure recursively.
244
     *
245
     * @param array $files Uploaded files array to be populated.
246
     * @param mixed $names File names provided by PHP.
247
     * @param mixed $tempNames Temporary file names provided by PHP.
248
     * @param mixed $types File types provided by PHP.
249
     * @param mixed $sizes File sizes provided by PHP.
250
     * @param mixed $errors Uploading issues provided by PHP.
251
     *
252
     * @psalm-suppress MixedArgument, ReferenceConstraintViolation
253
     */
254 6
    private function populateUploadedFileRecursive(
255
        array &$files,
256
        mixed $names,
257
        mixed $tempNames,
258
        mixed $types,
259
        mixed $sizes,
260
        mixed $errors
261
    ): void {
262 6
        if (is_array($names)) {
263
            /** @var array|string $name */
264 6
            foreach ($names as $i => $name) {
265 6
                $files[$i] = [];
266
                /** @psalm-suppress MixedArrayAccess */
267 6
                $this->populateUploadedFileRecursive(
268 6
                    $files[$i],
269 6
                    $name,
270 6
                    $tempNames[$i],
271 6
                    $types[$i],
272 6
                    $sizes[$i],
273 6
                    $errors[$i],
274
                );
275
            }
276
277 6
            return;
278
        }
279
280
        try {
281 6
            $stream = $this->streamFactory->createStreamFromFile($tempNames);
282 6
        } catch (RuntimeException $e) {
283 6
            $stream = $this->streamFactory->createStream();
284
        }
285
286 6
        $files = $this->uploadedFileFactory->createUploadedFile(
287 6
            $stream,
288 6
            (int) $sizes,
289 6
            (int) $errors,
290
            $names,
291
            $types
292
        );
293 6
    }
294
}
295