Completed
Push — master ( 2fd031...a9e724 )
by Tobias
02:23
created

ServerRequestCreator::getHeadersFromServer()   B

Complexity

Conditions 8
Paths 8

Size

Total Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 8

Importance

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