Issues (7)

src/ServerRequestFactory.php (2 issues)

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 strtolower;
28
use function substr;
29
use function ucwords;
30
31
/**
32
 * `ServerRequestFactory` creates an instance of a server request.
33
 *
34
 * @deprecated Will remove in the next major version.
35
 */
36
final class ServerRequestFactory
37
{
38 46
    public function __construct(
39
        private ServerRequestFactoryInterface $serverRequestFactory,
40
        private UriFactoryInterface $uriFactory,
41
        private UploadedFileFactoryInterface $uploadedFileFactory,
42
        private StreamFactoryInterface $streamFactory
43
    ) {
44 46
    }
45
46
    /**
47
     * Creates an instance of a server request from PHP superglobals.
48
     *
49
     * @return ServerRequestInterface The server request instance.
50
     */
51 18
    public function createFromGlobals(): ServerRequestInterface
52
    {
53
        /** @psalm-var array<string, string> $_SERVER */
54 18
        return $this->createFromParameters(
55 18
            $_SERVER,
56 18
            $this->getHeadersFromGlobals(),
57 18
            $_COOKIE,
58 18
            $_GET,
59 18
            $_POST,
60 18
            $_FILES,
61 18
            fopen('php://input', 'rb') ?: null
62 18
        );
63
    }
64
65
    /**
66
     * Creates an instance of a server request from custom parameters.
67
     *
68
     * @param resource|StreamInterface|string|null $body
69
     *
70
     * @psalm-param array<string, string> $server
71
     * @psalm-param array<string, string|string[]> $headers
72
     * @psalm-param mixed $body
73
     *
74
     * @return ServerRequestInterface The server request instance.
75
     */
76 46
    public function createFromParameters(
77
        array $server,
78
        array $headers = [],
79
        array $cookies = [],
80
        array $get = [],
81
        array $post = [],
82
        array $files = [],
83
        mixed $body = null
84
    ): ServerRequestInterface {
85 46
        $method = $server['REQUEST_METHOD'] ?? null;
86
87 46
        if ($method === null) {
88 1
            throw new RuntimeException('Unable to determine HTTP request method.');
89
        }
90
91 45
        $uri = $this->getUri($server);
92 45
        $request = $this->serverRequestFactory->createServerRequest($method, $uri, $server);
93
94 45
        foreach ($headers as $name => $value) {
95 9
            if ($name === 'Host' && $request->hasHeader('Host')) {
96 9
                continue;
97
            }
98
99 1
            $request = $request->withAddedHeader($name, $value);
100
        }
101
102 45
        $protocol = '1.1';
103 45
        if (array_key_exists('SERVER_PROTOCOL', $server) && $server['SERVER_PROTOCOL'] !== '') {
104 2
            $protocol = str_replace('HTTP/', '', $server['SERVER_PROTOCOL']);
105
        }
106
107 45
        $request = $request
108 45
            ->withProtocolVersion($protocol)
109 45
            ->withQueryParams($get)
0 ignored issues
show
The method withQueryParams() does not exist on Psr\Http\Message\MessageInterface. It seems like you code against a sub-type of Psr\Http\Message\MessageInterface such as Psr\Http\Message\ServerRequestInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

109
            ->/** @scrutinizer ignore-call */ withQueryParams($get)
Loading history...
110 45
            ->withParsedBody($post)
111 45
            ->withCookieParams($cookies)
112 45
            ->withUploadedFiles($this->getUploadedFilesArray($files))
113 45
        ;
114
115 45
        if ($body === null) {
116 17
            return $request;
117
        }
118
119 28
        if ($body instanceof StreamInterface) {
120 1
            return $request->withBody($body);
121
        }
122
123 27
        if (is_string($body)) {
124 1
            return $request->withBody($this->streamFactory->createStream($body));
125
        }
126
127 26
        if (is_resource($body)) {
128 19
            return $request->withBody($this->streamFactory->createStreamFromResource($body));
129
        }
130
131 7
        throw new InvalidArgumentException(
132 7
            'Body parameter for "ServerRequestFactory::createFromParameters()"'
133 7
            . 'must be instance of StreamInterface, resource or null.',
134 7
        );
135
    }
136
137
    /**
138
     * @psalm-param array<string, string> $server
139
     */
140 45
    private function getUri(array $server): UriInterface
141
    {
142 45
        $uri = $this->uriFactory->createUri();
143
144 45
        if (array_key_exists('HTTPS', $server) && $server['HTTPS'] !== '' && $server['HTTPS'] !== 'off') {
145 4
            $uri = $uri->withScheme('https');
146
        } else {
147 41
            $uri = $uri->withScheme('http');
148
        }
149
150 45
        $uri = isset($server['SERVER_PORT'])
151 2
            ? $uri->withPort((int)$server['SERVER_PORT'])
152 43
            : $uri->withPort($uri->getScheme() === 'https' ? 443 : 80);
153
154 45
        if (isset($server['HTTP_HOST'])) {
155 16
            $uri = preg_match('/^(.+):(\d+)$/', $server['HTTP_HOST'], $matches) === 1
156 6
                ? $uri
157 6
                    ->withHost($matches[1])
158 6
                    ->withPort((int) $matches[2])
159 10
                : $uri->withHost($server['HTTP_HOST'])
160 16
            ;
161 29
        } elseif (isset($server['SERVER_NAME'])) {
162 2
            $uri = $uri->withHost($server['SERVER_NAME']);
163
        }
164
165 45
        if (isset($server['REQUEST_URI'])) {
166 2
            $uri = $uri->withPath(explode('?', $server['REQUEST_URI'])[0]);
167
        }
168
169 45
        if (isset($server['QUERY_STRING'])) {
170 2
            $uri = $uri->withQuery($server['QUERY_STRING']);
171
        }
172
173 45
        return $uri;
174
    }
175
176
    /**
177
     * @psalm-return array<string, string>
178
     */
179 18
    private function getHeadersFromGlobals(): array
180
    {
181 18
        if (function_exists('getallheaders') && ($headers = getallheaders()) !== false) {
182
            /** @psalm-var array<string, string> $headers */
183
            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...
184
        }
185
186 18
        $headers = [];
187
188
        /**
189
         * @var string $name
190
         * @var string $value
191
         */
192 18
        foreach ($_SERVER as $name => $value) {
193 18
            if (str_starts_with($name, 'REDIRECT_')) {
194 1
                $name = substr($name, 9);
195
196 1
                if (array_key_exists($name, $_SERVER)) {
197 1
                    continue;
198
                }
199
            }
200
201 18
            if (str_starts_with($name, 'HTTP_')) {
202 9
                $headers[$this->normalizeHeaderName(substr($name, 5))] = $value;
203 9
                continue;
204
            }
205
206 18
            if (str_starts_with($name, 'CONTENT_')) {
207 1
                $headers[$this->normalizeHeaderName($name)] = $value;
208
            }
209
        }
210
211 18
        return $headers;
212
    }
213
214 9
    private function normalizeHeaderName(string $name): string
215
    {
216 9
        return str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $name))));
217
    }
218
219 45
    private function getUploadedFilesArray(array $filesArray): array
220
    {
221 45
        $files = [];
222
223
        /** @var array $info */
224 45
        foreach ($filesArray as $class => $info) {
225 17
            $files[$class] = [];
226 17
            $this->populateUploadedFileRecursive(
227 17
                $files[$class],
228 17
                $info['name'],
229 17
                $info['tmp_name'],
230 17
                $info['type'],
231 17
                $info['size'],
232 17
                $info['error'],
233 17
            );
234
        }
235
236 45
        return $files;
237
    }
238
239
    /**
240
     * Populates uploaded files array from $_FILE data structure recursively.
241
     *
242
     * @param array $files Uploaded files array to be populated.
243
     * @param mixed $names File names provided by PHP.
244
     * @param mixed $tempNames Temporary file names provided by PHP.
245
     * @param mixed $types File types provided by PHP.
246
     * @param mixed $sizes File sizes provided by PHP.
247
     * @param mixed $errors Uploading issues provided by PHP.
248
     *
249
     * @psalm-suppress MixedArgument, ReferenceConstraintViolation
250
     */
251 17
    private function populateUploadedFileRecursive(
252
        array &$files,
253
        mixed $names,
254
        mixed $tempNames,
255
        mixed $types,
256
        mixed $sizes,
257
        mixed $errors
258
    ): void {
259 17
        if (is_array($names)) {
260
            /** @var array|string $name */
261 17
            foreach ($names as $i => $name) {
262 17
                $files[$i] = [];
263
                /** @psalm-suppress MixedArrayAccess */
264 17
                $this->populateUploadedFileRecursive(
265 17
                    $files[$i],
266 17
                    $name,
267 17
                    $tempNames[$i],
268 17
                    $types[$i],
269 17
                    $sizes[$i],
270 17
                    $errors[$i],
271 17
                );
272
            }
273
274 17
            return;
275
        }
276
277
        try {
278 17
            $stream = $this->streamFactory->createStreamFromFile($tempNames);
279 17
        } catch (RuntimeException) {
280 17
            $stream = $this->streamFactory->createStream();
281
        }
282
283 17
        $files = $this->uploadedFileFactory->createUploadedFile(
284 17
            $stream,
285 17
            (int) $sizes,
286 17
            (int) $errors,
287 17
            $names,
288 17
            $types
289 17
        );
290
    }
291
}
292