ServerRequestFactory   A
last analyzed

Complexity

Total Complexity 39

Size/Duplication

Total Lines 201
Duplicated Lines 0 %

Test Coverage

Coverage 88.66%

Importance

Changes 6
Bugs 0 Features 0
Metric Value
wmc 39
eloc 99
c 6
b 0
f 0
dl 0
loc 201
ccs 86
cts 97
cp 0.8866
rs 9.28

8 Methods

Rating   Name   Duplication   Size   Complexity  
A createFromGlobals() 0 10 2
A getUploadedFilesArray() 0 9 2
A populateUploadedFileRecursive() 0 20 4
A __construct() 0 10 1
B getUri() 0 33 10
B createFromParameters() 0 43 11
A normalizeHeaderName() 0 3 1
B getHeadersFromGlobals() 0 30 8
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Web;
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 35
    public function __construct(
40
        ServerRequestFactoryInterface $serverRequestFactory,
41
        UriFactoryInterface $uriFactory,
42
        UploadedFileFactoryInterface $uploadedFileFactory,
43
        StreamFactoryInterface $streamFactory
44
    ) {
45 35
        $this->serverRequestFactory = $serverRequestFactory;
46 35
        $this->uriFactory = $uriFactory;
47 35
        $this->uploadedFileFactory = $uploadedFileFactory;
48 35
        $this->streamFactory = $streamFactory;
49 35
    }
50
51 18
    public function createFromGlobals(): ServerRequestInterface
52
    {
53 18
        return $this->createFromParameters(
54
            $_SERVER,
55 18
            $this->getHeadersFromGlobals(),
56
            $_COOKIE,
57
            $_GET,
58
            $_POST,
59
            $_FILES,
60 18
            fopen('php://input', 'rb') ?: null
61
        );
62
    }
63
64
    /**
65
     * @param array $server
66
     * @param array $headers
67
     * @param array $cookies
68
     * @param array $get
69
     * @param array $post
70
     * @param array $files
71
     * @param resource|StreamInterface|string|null $body
72
     *
73
     * @return ServerRequestInterface
74
     */
75 35
    public function createFromParameters(array $server, array $headers = [], array $cookies = [], array $get = [], array $post = [], array $files = [], $body = null): ServerRequestInterface
76
    {
77 35
        $method = $server['REQUEST_METHOD'] ?? null;
78 35
        if ($method === null) {
79 1
            throw new RuntimeException('Unable to determine HTTP request method.');
80
        }
81
82 34
        $uri = $this->getUri($server);
83 34
        $request = $this->serverRequestFactory->createServerRequest($method, $uri, $server);
84
85 34
        foreach ($headers as $name => $value) {
86 9
            if ($name === 'Host' && $request->hasHeader('Host')) {
87 9
                continue;
88
            }
89
90 1
            $request = $request->withAddedHeader($name, $value);
91
        }
92
93 34
        $protocol = '1.1';
94 34
        if (array_key_exists('SERVER_PROTOCOL', $server) && $server['SERVER_PROTOCOL'] !== '') {
95 2
            $protocol = str_replace('HTTP/', '', $server['SERVER_PROTOCOL']);
96
        }
97
98
        $request = $request
99 34
            ->withProtocolVersion($protocol)
100 34
            ->withQueryParams($get)
101 34
            ->withParsedBody($post)
102 34
            ->withCookieParams($cookies)
103 34
            ->withUploadedFiles($this->getUploadedFilesArray($files));
104
105 34
        if ($body === null) {
106 16
            return $request;
107
        }
108
109 18
        if (is_resource($body)) {
110 18
            $body = $this->streamFactory->createStreamFromResource($body);
111
        } elseif (is_string($body)) {
112
            $body = $this->streamFactory->createStream($body);
113
        } elseif (!$body instanceof StreamInterface) {
0 ignored issues
show
introduced by
$body is always a sub-type of Psr\Http\Message\StreamInterface.
Loading history...
114
            throw new InvalidArgumentException('Body parameter for ServerRequestFactory::createFromParameters() must be instance of StreamInterface, resource or null.');
115
        }
116
117 18
        return $request->withBody($body);
118
    }
119
120 34
    private function getUri(array $server): UriInterface
121
    {
122 34
        $uri = $this->uriFactory->createUri();
123
124 34
        if (array_key_exists('HTTPS', $server) && $server['HTTPS'] !== '' && $server['HTTPS'] !== 'off') {
125 4
            $uri = $uri->withScheme('https');
126
        } else {
127 30
            $uri = $uri->withScheme('http');
128
        }
129
130 34
        if (isset($server['HTTP_HOST'])) {
131 16
            if (1 === preg_match('/^(.+):(\d+)$/', $server['HTTP_HOST'], $matches)) {
132 6
                $uri = $uri->withHost($matches[1])->withPort((int) $matches[2]);
133
            } else {
134 16
                $uri = $uri->withHost($server['HTTP_HOST']);
135
            }
136 18
        } elseif (isset($server['SERVER_NAME'])) {
137 2
            $uri = $uri->withHost($server['SERVER_NAME']);
138
        }
139
140 34
        if (isset($server['SERVER_PORT'])) {
141 2
            $uri = $uri->withPort($server['SERVER_PORT']);
142
        }
143
144 34
        if (isset($server['REQUEST_URI'])) {
145 2
            $uri = $uri->withPath(explode('?', $server['REQUEST_URI'])[0]);
146
        }
147
148 34
        if (isset($server['QUERY_STRING'])) {
149 2
            $uri = $uri->withQuery($server['QUERY_STRING']);
150
        }
151
152 34
        return $uri;
153
    }
154
155 18
    private function getHeadersFromGlobals(): array
156
    {
157 18
        if (function_exists('getallheaders')) {
158
            $headers = getallheaders();
159
            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...
160
        }
161
162 18
        $headers = [];
163
164 18
        foreach ($_SERVER as $name => $value) {
165 18
            if (strncmp($name, 'REDIRECT_', 9) === 0) {
166 1
                $name = substr($name, 9);
167
168 1
                if (array_key_exists($name, $_SERVER)) {
169 1
                    continue;
170
                }
171
            }
172
173 18
            if (strncmp($name, 'HTTP_', 5) === 0) {
174 9
                $headers[$this->normalizeHeaderName(substr($name, 5))] = $value;
175 9
                continue;
176
            }
177
178 18
            if (strncmp($name, 'CONTENT_', 8) === 0) {
179 1
                $headers[$this->normalizeHeaderName($name)] = $value;
180 1
                continue;
181
            }
182
        }
183
184 18
        return $headers;
185
    }
186
187 9
    private function normalizeHeaderName(string $name): string
188
    {
189 9
        return str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $name))));
190
    }
191
192 34
    private function getUploadedFilesArray(array $filesArray): array
193
    {
194
        // TODO: simplify?
195 34
        $files = [];
196 34
        foreach ($filesArray as $class => $info) {
197 18
            $files[$class] = [];
198 18
            $this->populateUploadedFileRecursive($files[$class], $info['name'], $info['tmp_name'], $info['type'], $info['size'], $info['error']);
199
        }
200 34
        return $files;
201
    }
202
203
    /**
204
     * Populates uploaded files array from $_FILE data structure recursively.
205
     *
206
     * @param array $files uploaded files array to be populated.
207
     * @param mixed $names file names provided by PHP
208
     * @param mixed $tempNames temporary file names provided by PHP
209
     * @param mixed $types file types provided by PHP
210
     * @param mixed $sizes file sizes provided by PHP
211
     * @param mixed $errors uploading issues provided by PHP
212
     */
213 18
    private function populateUploadedFileRecursive(array &$files, $names, $tempNames, $types, $sizes, $errors): void
214
    {
215 18
        if (is_array($names)) {
216 18
            foreach ($names as $i => $name) {
217 18
                $files[$i] = [];
218 18
                $this->populateUploadedFileRecursive($files[$i], $name, $tempNames[$i], $types[$i], $sizes[$i], $errors[$i]);
219
            }
220
        } else {
221
            try {
222 18
                $stream = $this->streamFactory->createStreamFromFile($tempNames);
223 18
            } catch (RuntimeException $e) {
224 18
                $stream = $this->streamFactory->createStream();
225
            }
226
227 18
            $files = $this->uploadedFileFactory->createUploadedFile(
228 18
                $stream,
229 18
                (int)$sizes,
230 18
                (int)$errors,
231
                $names,
232
                $types
233
            );
234
        }
235 18
    }
236
}
237