ServerRequest::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 10
cts 10
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 18
nc 1
nop 9
crap 1

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
namespace Kambo\Http\Message;
3
4
// \Spl
5
use InvalidArgumentException;
6
7
// \Psr
8
use Psr\Http\Message\ServerRequestInterface;
9
use Psr\Http\Message\UriInterface;
10
use Psr\Http\Message\StreamInterface;
11
use Psr\Http\Message\UploadedFileInterface;
12
13
// \Http\Message
14
use Kambo\Http\Message\Uri;
15
use Kambo\Http\Message\Message;
16
use Kambo\Http\Message\UploadedFile;
17
use Kambo\Http\Message\Headers;
18
use Kambo\Http\Message\Parser\Parser;
19
use Kambo\Http\Message\RequestTrait;
20
21
/**
22
 * Representation of an incoming, server-side HTTP request.
23
 *
24
 * Per the HTTP specification, this class includes properties for
25
 * each of the following:
26
 *
27
 * - Protocol version
28
 * - HTTP method
29
 * - URI
30
 * - Headers
31
 * - Message body
32
 *
33
 * Additionally, it encapsulates all data as it has arrived to the
34
 * application from the CGI and/or PHP environment, including:
35
 *
36
 * - The values represented in $_SERVER.
37
 * - Any cookies provided (generally via $_COOKIE)
38
 * - Query string arguments (generally via $_GET, or as parsed via parse_str())
39
 * - Upload files, if any (as represented by $_FILES)
40
 * - Deserialized body parameters (generally from $_POST)
41
 *
42
 * $_SERVER values are treated as immutable, as they represent application
43
 * state at the time of request; as such, no methods are provided to allow
44
 * modification of those values. The other values provide such methods, as they
45
 * can be restored from $_SERVER or the request body, and may need treatment
46
 * during the application (e.g., body parameters may be deserialized based on
47
 * content type).
48
 *
49
 * Additionally, this class recognizes the utility of introspecting a
50
 * request to derive and match additional parameters (e.g., via URI path
51
 * matching, decrypting cookie values, deserializing non-form-encoded body
52
 * content, matching authorization headers to users, etc). These parameters
53
 * are stored in an "attributes" property.
54
 *
55
 * Requests are considered immutable; all methods that change state retain
56
 * the internal state of the current message and return an instance that
57
 * contains the changed state.
58
 *
59
 * @package Kambo\Http\Message
60
 * @author  Bohuslav Simek <[email protected]>
61
 * @license MIT
62
 */
63
class ServerRequest extends Message implements ServerRequestInterface
64
{
65
    use RequestTrait;
66
67
    /**
68
     * Server parameters - related to the incoming request environment, 
69
     * they are typically derived from PHP's $_SERVER superglobal.
70
     *
71
     * @var array
72
     */
73
    private $serverVariables;
74
75
    /**
76
     * Deserialized query string arguments, if any.
77
     *
78
     * @var array
79
     */
80
    private $queryParams = null;
81
82
    /**
83
     * Cookies sent by the client to the server.
84
     *
85
     * @var array
86
     */
87
    private $cookies;
88
89
    /**
90
     * Contain attributes derived from the request.
91
     *
92
     * @var array
93
     */
94
    private $attributes = [];
95
96
    /**
97
     * Uploaded files of incoming request, if any.
98
     *
99
     * @var array
100
     */
101
    private $uploadedFiles = null;
102
103
    /**
104
     * Parsed incoming request body - this value is filled 
105
     * when method getParsedBody or withParsedBody is called.
106
     *
107
     * @var array|object|null
108
     */
109
    private $parsedBody = null;
110
111
    /**
112
     * Create new HTTP request.
113
     *
114
     * Adds a host header when none was provided and a host is defined in uri.
115
     * 
116
     * @param string          $requestMethod   The request method
117
     * @param UriInterface    $uri             The request URI object
118
     * @param StreamInterface $body            The request body object
119
     * @param Headers|array   $headers         The request headers collection
120
     * @param array           $serverVariables The server environment variables
121
     * @param array           $cookies         The request cookies collection
122
     * @param array           $uploadFiles     The request uploadedFiles collection
123
     * @param string          $protocol        The request version of the protocol
124
     * @param array           $attributes      The request attributs
125
     *
126
     */
127 28
    public function __construct(
128
        $requestMethod,
129
        Uri $uri,
130
        StreamInterface $body,
131
        $headers,
132
        array $serverVariables,
133
        array $cookies,
134
        array $uploadFiles,
135
        $protocol,
136
        array $attributes = []
137
    ) {
138 28
        parent::__construct($headers, $body, $protocol);
139 28
        $this->validateMethod($requestMethod);
140 28
        $this->uri             = $uri;
141 28
        $this->cookies         = $cookies;
142 28
        $this->requestMethod   = $requestMethod;
143 28
        $this->uploadedFiles   = $uploadFiles;
144 28
        $this->attributes      = $attributes;
145 28
        $this->serverVariables = $serverVariables;
146 28
    }
147
148
    /**
149
     * Retrieve server parameters.
150
     *
151
     * Retrieves data related to the incoming request environment,
152
     * typically derived from PHP's $_SERVER superglobal. The data IS NOT
153
     * REQUIRED to originate from $_SERVER.
154
     *
155
     * @return array
156
     */
157 1
    public function getServerParams()
158
    {
159 1
        return $this->serverVariables;
160
    }
161
162
    /**
163
     * Retrieve cookies.
164
     *
165
     * Retrieves cookies sent by the client to the server.
166
     *
167
     * The data are compatible with the structure of the $_COOKIE
168
     * superglobal.
169
     *
170
     * @return array
171
     */
172 2
    public function getCookieParams()
173
    {
174 2
        return $this->cookies;
175
    }
176
177
    /**
178
     * Return an instance with the specified cookies.
179
     *
180
     * The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST
181
     * be compatible with the structure of $_COOKIE. Typically, this data will
182
     * be injected at instantiation.
183
     *
184
     * This method not update the related Cookie header of the request
185
     * instance, nor related values in the server params.
186
     *
187
     * This method retain the immutability of the message, and return an instance 
188
     * that has the updated cookie values.
189
     *
190
     * @param array $cookies Array of key/value pairs representing cookies.
191
     *
192
     * @return self for fluent interface
193
     */
194 1
    public function withCookieParams(array $cookies)
195
    {
196 1
        $clone          = clone $this;
197 1
        $clone->cookies = $cookies;
198
199 1
        return $clone;
200
    }
201
202
    /**
203
     * Retrieve query string arguments.
204
     *
205
     * Retrieves the deserialized query string arguments, if any.
206
     *
207
     * Note: the query params might not be in sync with the URI or server
208
     * params. If you need to ensure you are only getting the original
209
     * values, you may need to parse the query string from `getUri()->getQuery()`
210
     * or from the `QUERY_STRING` server param.
211
     *
212
     * @return array
213
     */
214 2
    public function getQueryParams()
215
    {
216 2
        if ($this->queryParams === null) {
217 2
            parse_str($this->uri->getQuery(), $this->queryParams);
218 2
        }
219
220 2
        return $this->queryParams;
221
    }
222
223
    /**
224
     * Return an instance with the specified query string arguments.
225
     *
226
     * These values remain immutable over the course of the incoming
227
     * request.
228
     *
229
     * Setting query string arguments not change the URI stored by the
230
     * request, nor the values in the server params.
231
     *
232
     * This method retain the immutability of the message, and return an instance 
233
     * that has the updated query string arguments.
234
     *
235
     * @param array $query Array of query string arguments, typically from $_GET.
236
     *
237
     * @return self for fluent interface
238
     */
239 1
    public function withQueryParams(array $query)
240
    {
241 1
        $clone              = clone $this;
242 1
        $clone->queryParams = $query;
243
244 1
        return $clone;
245
    }
246
247
    /**
248
     * Retrieve normalized file upload data.
249
     *
250
     * This method returns upload metadata in a normalized tree, with each leaf
251
     * an instance of Psr\Http\Message\UploadedFileInterface.
252
     *
253
     * These values can be prepared from $_FILES or the message body during
254
     * instantiation, or can be injected via withUploadedFiles().
255
     *
256
     * @return array An array tree of UploadedFileInterface instances; an empty
257
     *               array is returned if no data is present.
258
     */
259 2
    public function getUploadedFiles()
260
    {
261 2
        return $this->uploadedFiles;
262
    }
263
264
    /**
265
     * Create a new instance with the specified uploaded files.
266
     *
267
     * This method retain the immutability of the message, and return an instance 
268
     * that has the updated body parameters.
269
     *
270
     * @param array An array tree of UploadedFileInterface instances.
271
     *
272
     * @return self for fluent interface
273
     *
274
     * @throws \InvalidArgumentException if an invalid structure is provided.
275
     */
276 2
    public function withUploadedFiles(array $uploadedFiles)
277
    {
278 2
        $this->validateUploadedFiles($uploadedFiles);
279 1
        $clone                = clone $this;
280 1
        $clone->uploadedFiles = $uploadedFiles;
281
282 1
        return $clone;
283
    }
284
285
    /**
286
     * Retrieve any parameters provided in the request body.
287
     *
288
     * If the request Content-Type is either application/x-www-form-urlencoded
289
     * or multipart/form-data, and the request method is POST, this method
290
     * return the contents of $_POST.
291
     *
292
     * Otherwise, this method may return any results of deserializing
293
     * the request body content; as parsing returns structured content, the
294
     * potential types are arrays or objects. A null value indicates
295
     * the absence of body content.
296
     *
297
     * @return null|array|object The deserialized body parameters, if any.
298
     *                           These will typically be an array or object.
299
     */
300 5
    public function getParsedBody()
301
    {
302 5
        if ($this->body !== null && $this->parsedBody === null) {
303 3
            $parser = new Parser($this->getContentType());
304 3
            $this->parsedBody = $parser->parse($this->body);
305 3
        }
306
307 5
        return $this->parsedBody;
308
    }
309
310
    /**
311
     * Return an instance with the specified body parameters.
312
     *
313
     * If the request Content-Type is either application/x-www-form-urlencoded
314
     * or multipart/form-data, and the request method is POST, use this method
315
     * ONLY to inject the contents of $_POST.
316
     *
317
     * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of
318
     * deserializing the request body content. Deserialization/parsing returns
319
     * structured data, and, as such, this method ONLY accepts arrays or objects,
320
     * or a null value if nothing was available to parse.
321
     *
322
     * As an example, if content negotiation determines that the request data
323
     * is a JSON payload, this method could be used to create a request
324
     * instance with the deserialized parameters.
325
     *
326
     * This method retain the immutability of the message, and return an 
327
     * instance that has the updated body parameters.
328
     *     
329
     * @param null|array|object $data The deserialized body data. This will
330
     *                                typically be in an array or object.
331
     *
332
     * @return self for fluent interface
333
     *
334
     * @throws \InvalidArgumentException if an unsupported argument type is provided.    
335
     */
336 4
    public function withParsedBody($data)
337
    {
338 4
        if (!is_null($data) && !is_object($data) && !is_array($data)) {
339 1
            throw new InvalidArgumentException('Value must be an array, an object, or null');
340
        }
341
342 3
        $clone             = clone $this;
343 3
        $clone->parsedBody = $data;
344
345 3
        return $clone;
346
    }
347
348
    /**
349
     * Retrieve attributes derived from the request.
350
     *
351
     * The request "attributes" may be used to allow injection of any
352
     * parameters derived from the request: e.g., the results of path
353
     * match operations; the results of decrypting cookies; the results of
354
     * deserializing non-form-encoded message bodies; etc. Attributes
355
     * will be application and request specific, and is mutable.
356
     *
357
     * @return array Attributes derived from the request.
358
     */
359 1
    public function getAttributes()
360
    {
361 1
        return $this->attributes;
362
    }
363
364
    /**
365
     * Retrieve a single derived request attribute.
366
     *
367
     * Retrieves a single derived request attribute as described in
368
     * getAttributes(). If the attribute has not been previously set, returns
369
     * the default value as provided.
370
     *
371
     * This method obviates the need for a hasAttribute() method, as it allows
372
     * specifying a default value to return if the attribute is not found.
373
     *
374
     * @see getAttributes()
375
     *
376
     * @param string $name    The attribute name.
377
     * @param mixed  $default Default value to return if the attribute does not exist.
378
     *
379
     * @return mixed
380
     */
381 4
    public function getAttribute($name, $default = null)
382
    {
383 4
        $attributeValue = $default;
384 4
        if (isset($this->attributes[$name])) {
385 3
            $attributeValue = $this->attributes[$name];
386 3
        }
387
388 4
        return $attributeValue;
389
    }
390
391
    /**
392
     * Return an instance with the specified derived request attribute.
393
     *
394
     * This method allows setting a single derived request attribute as
395
     * described in getAttributes().
396
     *
397
     * This method retains the state of the current instance, and return
398
     * an instance that has the updated attribute.
399
     *
400
     * @see getAttributes()
401
     *
402
     * @param string $name  The attribute name.
403
     * @param mixed  $value The value of the attribute.
404
     *
405
     * @return self for fluent interface
406
     */
407 4
    public function withAttribute($name, $value)
408
    {
409 4
        $clone                    = clone $this;
410 4
        $clone->attributes[$name] = $value;
411
412 4
        return $clone;
413
    }
414
415
    /**
416
     * Return an instance that removes the specified derived request attribute.
417
     *
418
     * This method allows removing a single derived request attribute as
419
     * described in getAttributes().
420
     *
421
     * This method retains the state of the current instance, and return
422
     * an instance that removes the attribute.
423
     *
424
     * @see getAttributes()
425
     *
426
     * @param string $name The attribute name.
427
     *
428
     * @return self for fluent interface
429
     */
430 1
    public function withoutAttribute($name)
431
    {
432 1
        $clone = clone $this;
433
434 1
        if (isset($clone->attributes[$name])) {
435 1
            unset($clone->attributes[$name]);
436 1
        }
437
438 1
        return $clone;
439
    }
440
441
    // ------------ PRIVATE METHODS
442
443
    /**
444
     * Validate request method
445
     *
446
     * @param string $method request method
447
     *
448
     * @return self for fluent interface
449
     *
450
     * @throws \InvalidArgumentException if an unsupported method is provided.     
451
     */
452 28
    private function validateMethod($method)
453
    {
454
        $valid = [
455 28
            'GET'    => true,
456 28
            'POST'   => true,
457 28
            'DELETE' => true,
458 28
            'PUT'    => true,
459
            'PATCH'  => true
460 28
        ];
461
462 28
        if (!isset($valid[$method])) {
463 1
            throw new InvalidArgumentException(
464
                'Invalid method version. Must be one of: GET, POST, DELTE, PUT or PATCH'
465 1
            );
466
        }
467 28
    }
468
469
    /**
470
     * Get request content type.
471
     *
472
     * @return string|null The request content type, if known
473
     */
474 3
    private function getContentType()
475
    {
476 3
        $result = null;
477 3
        if ($this->hasHeader('Content-Type')) {
478 2
            $result = $this->getHeader('Content-Type')[0];
479 2
        }
480
481 3
        return $result;
482
    }
483
484
    /**
485
     * Validate the structure in an uploaded files array.
486
     *
487
     * @param array $uploadedFiles
488
     *
489
     * @throws InvalidArgumentException if any entry is not an UploadedFileInterface instance.
490
     */
491 2
    private function validateUploadedFiles(array $uploadedFiles)
492
    {
493 2
        foreach ($uploadedFiles as $file) {
494 2
            if (is_array($file)) {
495 1
                $this->validateUploadedFiles($file);
496 2
            } elseif (!$file instanceof UploadedFileInterface) {
497 1
                throw new InvalidArgumentException('Invalid entry in uploaded files structure');
498
            }
499 1
        }
500 1
    }
501
}
502