Passed
Push — master ( 5c9806...4102cb )
by Alexander
04:15 queued 01:10
created

ServerRequestFactory::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 4
c 1
b 0
f 1
nc 1
nop 4
dl 0
loc 10
ccs 5
cts 5
cp 1
crap 1
rs 10
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 1
            $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') && ($headers = getallheaders()) !== false) {
184
            /** @psalm-var array<string, string> $headers */
185
            return $headers;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $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...
186
        }
187
188 22
        $headers = [];
189
190
        /**
191
         * @var string $name
192
         * @var string $value
193
         */
194 22
        foreach ($_SERVER as $name => $value) {
195 22
            if (strncmp($name, 'REDIRECT_', 9) === 0) {
196 1
                $name = substr($name, 9);
197
198 1
                if (array_key_exists($name, $_SERVER)) {
199 1
                    continue;
200
                }
201
            }
202
203 22
            if (strncmp($name, 'HTTP_', 5) === 0) {
204 13
                $headers[$this->normalizeHeaderName(substr($name, 5))] = $value;
205 13
                continue;
206
            }
207
208 22
            if (strncmp($name, 'CONTENT_', 8) === 0) {
209 1
                $headers[$this->normalizeHeaderName($name)] = $value;
210
            }
211
        }
212
213 22
        return $headers;
214
    }
215
216 13
    private function normalizeHeaderName(string $name): string
217
    {
218 13
        return str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $name))));
219
    }
220
221 49
    private function getUploadedFilesArray(array $filesArray): array
222
    {
223 49
        $files = [];
224
225
        /** @var array $info */
226 49
        foreach ($filesArray as $class => $info) {
227 5
            $files[$class] = [];
228 5
            $this->populateUploadedFileRecursive(
229 5
                $files[$class],
230 5
                $info['name'],
231 5
                $info['tmp_name'],
232 5
                $info['type'],
233 5
                $info['size'],
234 5
                $info['error'],
235
            );
236
        }
237
238 49
        return $files;
239
    }
240
241
    /**
242
     * Populates uploaded files array from $_FILE data structure recursively.
243
     *
244
     * @param array $files Uploaded files array to be populated.
245
     * @param mixed $names File names provided by PHP.
246
     * @param mixed $tempNames Temporary file names provided by PHP.
247
     * @param mixed $types File types provided by PHP.
248
     * @param mixed $sizes File sizes provided by PHP.
249
     * @param mixed $errors Uploading issues provided by PHP.
250
     *
251
     * @psalm-suppress MixedArgument, ReferenceConstraintViolation
252
     */
253 5
    private function populateUploadedFileRecursive(
254
        array &$files,
255
        mixed $names,
256
        mixed $tempNames,
257
        mixed $types,
258
        mixed $sizes,
259
        mixed $errors
260
    ): void {
261 5
        if (is_array($names)) {
262
            /** @var array|string $name */
263 5
            foreach ($names as $i => $name) {
264 5
                $files[$i] = [];
265
                /** @psalm-suppress MixedArrayAccess */
266 5
                $this->populateUploadedFileRecursive(
267 5
                    $files[$i],
268 5
                    $name,
269 5
                    $tempNames[$i],
270 5
                    $types[$i],
271 5
                    $sizes[$i],
272 5
                    $errors[$i],
273
                );
274
            }
275
276 5
            return;
277
        }
278
279
        try {
280 5
            $stream = $this->streamFactory->createStreamFromFile($tempNames);
281 5
        } catch (RuntimeException $e) {
282 5
            $stream = $this->streamFactory->createStream();
283
        }
284
285 5
        $files = $this->uploadedFileFactory->createUploadedFile(
286 5
            $stream,
287 5
            (int) $sizes,
288 5
            (int) $errors,
289
            $names,
290
            $types
291
        );
292 5
    }
293
}
294