Completed
Push — master ( e6a526...60dcbc )
by Tobias
02:12
created

ServerRequestCreator::fromGlobals()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 0
cts 6
cp 0
rs 9.9
c 0
b 0
f 0
cc 4
nc 4
nop 0
crap 20
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Nyholm\Psr7Server;
6
7
use Psr\Http\Message\ServerRequestFactoryInterface;
8
use Psr\Http\Message\ServerRequestInterface;
9
use Psr\Http\Message\StreamFactoryInterface;
10
use Psr\Http\Message\StreamInterface;
11
use Psr\Http\Message\UploadedFileFactoryInterface;
12
use Psr\Http\Message\UploadedFileInterface;
13
use Psr\Http\Message\UriFactoryInterface;
14
use Psr\Http\Message\UriInterface;
15
16
/**
17
 * @author Tobias Nyholm <[email protected]>
18
 * @author Martijn van der Ven <[email protected]>
19
 */
20
final class ServerRequestCreator implements ServerRequestCreatorInterface
21
{
22
    private $serverRequestFactory;
23
24
    private $uriFactory;
25
26
    private $uploadedFileFactory;
27
28
    private $streamFactory;
29
30 21
    public function __construct(
31
        ServerRequestFactoryInterface $serverRequestFactory,
32
        UriFactoryInterface $uriFactory,
33
        UploadedFileFactoryInterface $uploadedFileFactory,
34
        StreamFactoryInterface $streamFactory
35
    ) {
36 21
        $this->serverRequestFactory = $serverRequestFactory;
37 21
        $this->uriFactory = $uriFactory;
38 21
        $this->uploadedFileFactory = $uploadedFileFactory;
39 21
        $this->streamFactory = $streamFactory;
40 21
    }
41
42
    /**
43
     * {@inheritdoc}
44
     */
45
    public function fromGlobals(): ServerRequestInterface
46
    {
47
        $server = $_SERVER;
48
        if (false === isset($server['REQUEST_METHOD'])) {
49
            $server['REQUEST_METHOD'] = 'GET';
50
        }
51
52
        $headers = \function_exists('getallheaders') ? getallheaders() : static::getHeadersFromServer($_SERVER);
53
54
        return $this->fromArrays($server, $headers, $_COOKIE, $_GET, $_POST, $_FILES, \fopen('php://input', 'r') ?: null);
55
    }
56
57
    /**
58
     * {@inheritdoc}
59
     */
60 8
    public function fromArrays(array $server, array $headers = [], array $cookie = [], array $get = [], array $post = [], array $files = [], $body = null): ServerRequestInterface
61
    {
62 8
        $method = $this->getMethodFromEnv($server);
63 8
        $uri = $this->getUriFromEnvWithHTTP($server);
64 8
        $protocol = isset($server['SERVER_PROTOCOL']) ? \str_replace('HTTP/', '', $server['SERVER_PROTOCOL']) : '1.1';
65
66 8
        $serverRequest = $this->serverRequestFactory->createServerRequest($method, $uri, $server);
67 8
        foreach ($headers as $name => $value) {
68
            // Because PHP automatically casts array keys set with numeric strings to integers, we have to make sure
69
            // that numeric headers will not be sent along as integers, as withAddedHeader can only accept strings.
70 1
            if (\is_int($name)) {
71 1
                $name = (string) $name;
72
            }
73 1
            $serverRequest = $serverRequest->withAddedHeader($name, $value);
74
        }
75
76
        $serverRequest = $serverRequest
77 8
            ->withProtocolVersion($protocol)
78 8
            ->withCookieParams($cookie)
79 8
            ->withQueryParams($get)
80 8
            ->withParsedBody($post)
81 8
            ->withUploadedFiles($this->normalizeFiles($files));
82
83 7
        if (null === $body) {
84 6
            return $serverRequest;
85
        }
86
87 1
        if (\is_resource($body)) {
88
            $body = $this->streamFactory->createStreamFromResource($body);
89 1
        } elseif (\is_string($body)) {
90 1
            $body = $this->streamFactory->createStream($body);
91
        } elseif (!$body instanceof StreamInterface) {
92
            throw new \InvalidArgumentException('The $body parameter to ServerRequestCreator::fromArrays must be string, resource or StreamInterface');
93
        }
94
95 1
        return $serverRequest->withBody($body);
96
    }
97
98
    /**
99
     * Implementation from Zend\Diactoros\marshalHeadersFromSapi().
100
     */
101 2
    public static function getHeadersFromServer(array $server): array
102
    {
103 2
        $headers = [];
104 2
        foreach ($server as $key => $value) {
105
            // Apache prefixes environment variables with REDIRECT_
106
            // if they are added by rewrite rules
107 2
            if (0 === \strpos($key, 'REDIRECT_')) {
108 1
                $key = \substr($key, 9);
109
110
                // We will not overwrite existing variables with the
111
                // prefixed versions, though
112 1
                if (\array_key_exists($key, $server)) {
113 1
                    continue;
114
                }
115
            }
116
117 2
            if ($value && 0 === \strpos($key, 'HTTP_')) {
118 2
                $name = \strtr(\strtolower(\substr($key, 5)), '_', '-');
119 2
                $headers[$name] = $value;
120
121 2
                continue;
122
            }
123
124 1
            if ($value && 0 === \strpos($key, 'CONTENT_')) {
125 1
                $name = 'content-'.\strtolower(\substr($key, 8));
126 1
                $headers[$name] = $value;
127
128 1
                continue;
129
            }
130
        }
131
132 2
        return $headers;
133
    }
134
135 8
    private function getMethodFromEnv(array $environment): string
136
    {
137 8
        if (false === isset($environment['REQUEST_METHOD'])) {
138
            throw new \InvalidArgumentException('Cannot determine HTTP method');
139
        }
140
141 8
        return $environment['REQUEST_METHOD'];
142
    }
143
144 8
    private function getUriFromEnvWithHTTP(array $environment): UriInterface
145
    {
146 8
        $uri = $this->createUriFromArray($environment);
147 8
        if (empty($uri->getScheme())) {
148 7
            $uri = $uri->withScheme('http');
149
        }
150
151 8
        return $uri;
152
    }
153
154
    /**
155
     * Return an UploadedFile instance array.
156
     *
157
     * @param array $files A array which respect $_FILES structure
158
     *
159
     * @return UploadedFileInterface[]
160
     *
161
     * @throws \InvalidArgumentException for unrecognized values
162
     */
163 8
    private function normalizeFiles(array $files): array
164
    {
165 8
        $normalized = [];
166
167 8
        foreach ($files as $key => $value) {
168 7
            if ($value instanceof UploadedFileInterface) {
169 2
                $normalized[$key] = $value;
170 6
            } elseif (\is_array($value) && isset($value['tmp_name'])) {
171 4
                $normalized[$key] = $this->createUploadedFileFromSpec($value);
172 2
            } elseif (\is_array($value)) {
173 1
                $normalized[$key] = $this->normalizeFiles($value);
174
            } else {
175 7
                throw new \InvalidArgumentException('Invalid value in files specification');
176
            }
177
        }
178
179 7
        return $normalized;
180
    }
181
182
    /**
183
     * Create and return an UploadedFile instance from a $_FILES specification.
184
     *
185
     * If the specification represents an array of values, this method will
186
     * delegate to normalizeNestedFileSpec() and return that return value.
187
     *
188
     * @param array $value $_FILES struct
189
     *
190
     * @return array|UploadedFileInterface
191
     */
192 5
    private function createUploadedFileFromSpec(array $value)
193
    {
194 5
        if (\is_array($value['tmp_name'])) {
195 1
            return $this->normalizeNestedFileSpec($value);
196
        }
197
198
        try {
199 5
            $stream = $this->streamFactory->createStreamFromFile($value['tmp_name']);
200 1
        } catch (\RuntimeException $e) {
201 1
            $stream = $this->streamFactory->createStream();
202
        }
203
204 5
        return $this->uploadedFileFactory->createUploadedFile(
205 5
            $stream,
206 5
            (int) $value['size'],
207 5
            (int) $value['error'],
208 5
            $value['name'],
209 5
            $value['type']
210
        );
211
    }
212
213
    /**
214
     * Normalize an array of file specifications.
215
     *
216
     * Loops through all nested files and returns a normalized array of
217
     * UploadedFileInterface instances.
218
     *
219
     * @param array $files
220
     *
221
     * @return UploadedFileInterface[]
222
     */
223 1
    private function normalizeNestedFileSpec(array $files = []): array
224
    {
225 1
        $normalizedFiles = [];
226
227 1
        foreach (\array_keys($files['tmp_name']) as $key) {
228
            $spec = [
229 1
                'tmp_name' => $files['tmp_name'][$key],
230 1
                'size' => $files['size'][$key],
231 1
                'error' => $files['error'][$key],
232 1
                'name' => $files['name'][$key],
233 1
                'type' => $files['type'][$key],
234
            ];
235 1
            $normalizedFiles[$key] = $this->createUploadedFileFromSpec($spec);
236
        }
237
238 1
        return $normalizedFiles;
239
    }
240
241
    /**
242
     * Create a new uri from server variable.
243
     *
244
     * @param array $server typically $_SERVER or similar structure
245
     *
246
     * @return UriInterface
247
     */
248 18
    private function createUriFromArray(array $server): UriInterface
249
    {
250 18
        $uri = $this->uriFactory->createUri('');
251
252 18
        if (isset($server['HTTP_X_FORWARDED_PROTO'])) {
253 1
            $uri = $uri->withScheme($server['HTTP_X_FORWARDED_PROTO']);
254
        } else {
255 17
            if (isset($server['REQUEST_SCHEME'])) {
256
                $uri = $uri->withScheme($server['REQUEST_SCHEME']);
257 17
            } elseif (isset($server['HTTPS'])) {
258 9
                $uri = $uri->withScheme('on' === $server['HTTPS'] ? 'https' : 'http');
259
            }
260
261 17
            if (isset($server['SERVER_PORT'])) {
262 9
                $uri = $uri->withPort($server['SERVER_PORT']);
263
            }
264
        }
265
266 18
        if (isset($server['HTTP_HOST'])) {
267 9
            if (1 === \preg_match('/^(.+)\:(\d+)$/', $server['HTTP_HOST'], $matches)) {
268 3
                $uri = $uri->withHost($matches[1])->withPort($matches[2]);
269
            } else {
270 9
                $uri = $uri->withHost($server['HTTP_HOST']);
271
            }
272 9
        } elseif (isset($server['SERVER_NAME'])) {
273 1
            $uri = $uri->withHost($server['SERVER_NAME']);
274
        }
275
276 18
        if (isset($server['REQUEST_URI'])) {
277 10
            $uri = $uri->withPath(\current(\explode('?', $server['REQUEST_URI'])));
278
        }
279
280 18
        if (isset($server['QUERY_STRING'])) {
281 10
            $uri = $uri->withQuery($server['QUERY_STRING']);
282
        }
283
284 18
        return $uri;
285
    }
286
}
287