Completed
Pull Request — master (#1867)
by Bloody
02:15
created

ServerRequest::withParsedBody()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace GuzzleHttp\Psr7;
4
5
use InvalidArgumentException;
6
use Psr\Http\Message\ServerRequestInterface;
7
use Psr\Http\Message\UriInterface;
8
use Psr\Http\Message\StreamInterface;
9
use Psr\Http\Message\UploadedFileInterface;
10
11
/**
12
 * Server-side HTTP request
13
 *
14
 * Extends the Request definition to add methods for accessing incoming data,
15
 * specifically server parameters, cookies, matched path parameters, query
16
 * string arguments, body parameters, and upload file information.
17
 *
18
 * "Attributes" are discovered via decomposing the request (and usually
19
 * specifically the URI path), and typically will be injected by the application.
20
 *
21
 * Requests are considered immutable; all methods that might change state are
22
 * implemented such that they retain the internal state of the current
23
 * message and return a new instance that contains the changed state.
24
 */
25
class ServerRequest extends Request implements ServerRequestInterface
26
{
27
    /**
28
     * @var array
29
     */
30
    private $attributes = [];
31
32
    /**
33
     * @var array
34
     */
35
    private $cookieParams = [];
36
37
    /**
38
     * @var null|array|object
39
     */
40
    private $parsedBody;
41
42
    /**
43
     * @var array
44
     */
45
    private $queryParams = [];
46
47
    /**
48
     * @var array
49
     */
50
    private $serverParams;
51
52
    /**
53
     * @var array
54
     */
55
    private $uploadedFiles = [];
56
57
    /**
58
     * @param string                               $method       HTTP method
59
     * @param string|UriInterface                  $uri          URI
60
     * @param array                                $headers      Request headers
61
     * @param string|null|resource|StreamInterface $body         Request body
62
     * @param string                               $version      Protocol version
63
     * @param array                                $serverParams Typically the $_SERVER superglobal
64
     */
65
    public function __construct(
66
        $method,
67
        $uri,
68
        array $headers = [],
69
        $body = null,
70
        $version = '1.1',
71
        array $serverParams = []
72
    ) {
73
        $this->serverParams = $serverParams;
74
75
        parent::__construct($method, $uri, $headers, $body, $version);
76
    }
77
78
    /**
79
     * Return an UploadedFile instance array.
80
     *
81
     * @param array $files A array which respect $_FILES structure
82
     * @throws InvalidArgumentException for unrecognized values
83
     * @return array
84
     */
85
    public static function normalizeFiles(array $files)
86
    {
87
        $normalized = [];
88
89
        foreach ($files as $key => $value) {
90
            if ($value instanceof UploadedFileInterface) {
91
                $normalized[$key] = $value;
92
            } elseif (is_array($value) && isset($value['tmp_name'])) {
93
                $normalized[$key] = self::createUploadedFileFromSpec($value);
94
            } elseif (is_array($value)) {
95
                $normalized[$key] = self::normalizeFiles($value);
96
                continue;
97
            } else {
98
                throw new InvalidArgumentException('Invalid value in files specification');
99
            }
100
        }
101
102
        return $normalized;
103
    }
104
105
    /**
106
     * Create and return an UploadedFile instance from a $_FILES specification.
107
     *
108
     * If the specification represents an array of values, this method will
109
     * delegate to normalizeNestedFileSpec() and return that return value.
110
     *
111
     * @param array $value $_FILES struct
112
     * @return array|UploadedFileInterface
113
     */
114
    private static function createUploadedFileFromSpec(array $value)
115
    {
116
        if (is_array($value['tmp_name'])) {
117
            return self::normalizeNestedFileSpec($value);
118
        }
119
120
        return new UploadedFile(
121
            $value['tmp_name'],
122
            (int) $value['size'],
123
            (int) $value['error'],
124
            $value['name'],
125
            $value['type']
126
        );
127
    }
128
129
    /**
130
     * Normalize an array of file specifications.
131
     *
132
     * Loops through all nested files and returns a normalized array of
133
     * UploadedFileInterface instances.
134
     *
135
     * @param array $files
136
     * @return UploadedFileInterface[]
137
     */
138
    private static function normalizeNestedFileSpec(array $files = [])
139
    {
140
        $normalizedFiles = [];
141
142
        foreach (array_keys($files['tmp_name']) as $key) {
143
            $spec = [
144
                'tmp_name' => $files['tmp_name'][$key],
145
                'size'     => $files['size'][$key],
146
                'error'    => $files['error'][$key],
147
                'name'     => $files['name'][$key],
148
                'type'     => $files['type'][$key],
149
            ];
150
            $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec);
151
        }
152
153
        return $normalizedFiles;
154
    }
155
156
    /**
157
     * Return a ServerRequest populated with superglobals:
158
     * $_GET
159
     * $_POST
160
     * $_COOKIE
161
     * $_FILES
162
     * $_SERVER
163
     *
164
     * @return ServerRequestInterface
165
     */
166
    public static function fromGlobals()
167
    {
168
        $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
169
        $headers = getallheaders();
170
        $uri = self::getUriFromGlobals();
171
        $body = new LazyOpenStream('php://input', 'r+');
172
        $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1';
173
174
        $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER);
175
176
        return $serverRequest
177
            ->withCookieParams($_COOKIE)
178
            ->withQueryParams($_GET)
179
            ->withParsedBody($_POST)
180
            ->withUploadedFiles(self::normalizeFiles($_FILES));
181
    }
182
183
    private static function extractHostAndPortFromAuthority($authority)
184
    {
185
        $uri = 'http://'.$authority;
186
        $parts = parse_url($uri);
187
        if (false === $parts) {
188
            return [null, null];
189
        }
190
191
        $host = isset($parts['host']) ? $parts['host'] : null;
192
        $port = isset($parts['port']) ? $parts['port'] : null;
193
194
        return [$host, $port];
195
    }
196
197
    /**
198
     * Get a Uri populated with values from $_SERVER.
199
     *
200
     * @return UriInterface
201
     */
202
    public static function getUriFromGlobals()
203
    {
204
        $uri = new Uri('');
205
206
        $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http');
207
208
        $hasPort = false;
209
        if (isset($_SERVER['HTTP_HOST'])) {
210
            list($host, $port) = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']);
211
            if ($host !== null) {
212
                $uri = $uri->withHost($host);
213
            }
214
215
            if ($port !== null) {
216
                $hasPort = true;
217
                $uri = $uri->withPort($port);
218
            }
219
        } elseif (isset($_SERVER['SERVER_NAME'])) {
220
            $uri = $uri->withHost($_SERVER['SERVER_NAME']);
221
        } elseif (isset($_SERVER['SERVER_ADDR'])) {
222
            $uri = $uri->withHost($_SERVER['SERVER_ADDR']);
223
        }
224
225
        if (!$hasPort && isset($_SERVER['SERVER_PORT'])) {
226
            $uri = $uri->withPort($_SERVER['SERVER_PORT']);
227
        }
228
229
        $hasQuery = false;
230
        if (isset($_SERVER['REQUEST_URI'])) {
231
            $requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2);
232
            $uri = $uri->withPath($requestUriParts[0]);
233
            if (isset($requestUriParts[1])) {
234
                $hasQuery = true;
235
                $uri = $uri->withQuery($requestUriParts[1]);
236
            }
237
        }
238
239
        if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) {
240
            $uri = $uri->withQuery($_SERVER['QUERY_STRING']);
241
        }
242
243
        return $uri;
244
    }
245
246
247
    /**
248
     * {@inheritdoc}
249
     */
250
    public function getServerParams()
251
    {
252
        return $this->serverParams;
253
    }
254
255
    /**
256
     * {@inheritdoc}
257
     */
258
    public function getUploadedFiles()
259
    {
260
        return $this->uploadedFiles;
261
    }
262
263
    /**
264
     * {@inheritdoc}
265
     */
266
    public function withUploadedFiles(array $uploadedFiles)
267
    {
268
        $new = clone $this;
269
        $new->uploadedFiles = $uploadedFiles;
270
271
        return $new;
272
    }
273
274
    /**
275
     * {@inheritdoc}
276
     */
277
    public function getCookieParams()
278
    {
279
        return $this->cookieParams;
280
    }
281
282
    /**
283
     * {@inheritdoc}
284
     */
285
    public function withCookieParams(array $cookies)
286
    {
287
        $new = clone $this;
288
        $new->cookieParams = $cookies;
289
290
        return $new;
291
    }
292
293
    /**
294
     * {@inheritdoc}
295
     */
296
    public function getQueryParams()
297
    {
298
        return $this->queryParams;
299
    }
300
301
    /**
302
     * {@inheritdoc}
303
     */
304
    public function withQueryParams(array $query)
305
    {
306
        $new = clone $this;
307
        $new->queryParams = $query;
308
309
        return $new;
310
    }
311
312
    /**
313
     * {@inheritdoc}
314
     */
315
    public function getParsedBody()
316
    {
317
        return $this->parsedBody;
318
    }
319
320
    /**
321
     * {@inheritdoc}
322
     */
323
    public function withParsedBody($data)
324
    {
325
        $new = clone $this;
326
        $new->parsedBody = $data;
327
328
        return $new;
329
    }
330
331
    /**
332
     * {@inheritdoc}
333
     */
334
    public function getAttributes()
335
    {
336
        return $this->attributes;
337
    }
338
339
    /**
340
     * {@inheritdoc}
341
     */
342
    public function getAttribute($attribute, $default = null)
343
    {
344
        if (false === array_key_exists($attribute, $this->attributes)) {
345
            return $default;
346
        }
347
348
        return $this->attributes[$attribute];
349
    }
350
351
    /**
352
     * {@inheritdoc}
353
     */
354
    public function withAttribute($attribute, $value)
355
    {
356
        $new = clone $this;
357
        $new->attributes[$attribute] = $value;
358
359
        return $new;
360
    }
361
362
    /**
363
     * {@inheritdoc}
364
     */
365
    public function withoutAttribute($attribute)
366
    {
367
        if (false === array_key_exists($attribute, $this->attributes)) {
368
            return $this;
369
        }
370
371
        $new = clone $this;
372
        unset($new->attributes[$attribute]);
373
374
        return $new;
375
    }
376
}
377