Passed
Push — master ( 69b17c...082853 )
by Terry
02:44
created

ServerRequest   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 337
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 36
eloc 83
c 2
b 0
f 0
dl 0
loc 337
ccs 86
cts 86
cp 1
rs 9.52

17 Methods

Rating   Name   Duplication   Size   Complexity  
A withoutAttribute() 0 9 2
A withAttribute() 0 6 1
A getQueryParams() 0 3 1
A getUploadedFiles() 0 3 1
A withUploadedFiles() 0 8 1
A getAttributes() 0 3 1
A assertUploadedFiles() 0 8 4
A getParsedBody() 0 3 1
A getServerParams() 0 3 1
A getCookieParams() 0 3 1
A withQueryParams() 0 6 1
A __construct() 0 28 2
A getAttribute() 0 3 2
B determineParsedBody() 0 53 11
A withCookieParams() 0 6 1
A withParsedBody() 0 8 1
A assertParsedBody() 0 11 4
1
<?php 
2
/**
3
 * This file is part of the Shieldon package.
4
 *
5
 * (c) Terry L. <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
declare(strict_types=1);
12
13
namespace Shieldon\Psr7;
14
15
use Psr\Http\Message\ServerRequestInterface;
16
use Psr\Http\Message\UploadedFileInterface;
17
use Psr\Http\Message\UriInterface;
18
use Psr\Http\Message\StreamInterface;
19
use Shieldon\Psr7\Request;
20
use Shieldon\Psr7\Utils\UploadedFileHelper;
21
use InvalidArgumentException;
22
use function file_get_contents;
23
use function gettype;
24
use function is_array;
25
use function is_null;
26
use function is_object;
27
use function json_decode;
28
use function json_last_error;
29
use function parse_str;
30
use function preg_split;
31
use function sprintf;
32
use function strtolower;
33
use function strtoupper;
34
use const JSON_ERROR_NONE;
35
36
/*
37
 * Representation of an incoming, server-side HTTP request.
38
 */
39
class ServerRequest extends Request implements ServerRequestInterface
40
{
41
    /**
42
     * Typically derived from PHP's $_SERVER superglobal.
43
     * 
44
     * @var array
45
     */
46
    protected $serverParams;
47
48
    /**
49
     * Typically derived from PHP's $_COOKIE superglobal.
50
     * 
51
     * @var array
52
     */
53
    protected $cookieParams;
54
55
    /**
56
     * Typically derived from PHP's $_POST superglobal.
57
     * 
58
     * @var array|object|null
59
     */
60
    protected $parsedBody;
61
62
    /**
63
     * Typically derived from PHP's $_GET superglobal.
64
     * 
65
     * @var array
66
     */
67
    protected $queryParams;
68
69
    /**
70
     * Typically derived from PHP's $_FILES superglobal.
71
     * A collection of uploadFileInterface instances.
72
     * 
73
     * @var array
74
     */
75
    protected $uploadedFiles;
76
77
    /**
78
     * The request "attributes" may be used to allow injection of any
79
     * parameters derived from the request: e.g., the results of path
80
     * match operations; the results of decrypting cookies; the results of
81
     * deserializing non-form-encoded message bodies; etc. Attributes
82
     * will be application and request specific, and CAN be mutable.
83
     *
84
     * @var array
85
     */
86
    protected $attributes;
87
88
    /**
89
     * ServerRequest constructor.
90
     *
91
     * @param string                 $method       Request HTTP method
92
     * @param string|UriInterface    $uri          Request URI object URI or URL
93
     * @param string|StreamInterface $body         Request body
94
     * @param array                  $headers      Request headers
95
     * @param string                 $version      Request protocol version
96
     * @param array                  $serverParams Typically $_SERVER superglobal
97
     * @param array                  $cookieParams Typically $_COOKIE superglobal
98
     * @param array                  $postParams   Typically $_POST superglobal
99
     * @param array                  $getParams    Typically $_GET superglobal
100
     * @param array                  $filesParams  Typically $_FILES superglobal
101
     */
102 11
    public function __construct(
103
        string $method       = 'GET',
104
               $uri          = ''   ,
105
               $body         = ''   ,
106
        array  $headers      = []   ,
107
        string $version      = '1.1',
108
        array  $serverParams = []   ,
109
        array  $cookieParams = []   ,
110
        array  $postParams   = []   ,
111
        array  $getParams    = []   ,
112
        array  $filesParams  = []
113
    ) {
114 11
        parent::__construct($method, $uri, $body, $headers, $version);
115
116 11
        $this->serverParams = $serverParams;
117 11
        $this->cookieParams = $cookieParams;
118 11
        $this->queryParams  = $getParams;
119 11
        $this->attributes   = [];
120
121 11
        $this->determineParsedBody($postParams);
122
123
        // This property will be assigned to a parsed array that contains 
124
        // the UploadedFile instance(s) as the $filesParams is given.
125 11
        $this->uploadedFiles = [];
126
127 11
        if (!empty($filesParams)) {
128 2
            $this->uploadedFiles = UploadedFileHelper::uploadedFileSpecsConvert(
129 2
                UploadedFileHelper::uploadedFileParse($filesParams)
130 2
            );
131
        }
132
    }
133
134
    /**
135
     * {@inheritdoc}
136
     */
137 2
    public function getServerParams(): array
138
    {
139 2
        return $this->serverParams;
140
    }
141
142
    /**
143
     * {@inheritdoc}
144
     */
145 2
    public function getCookieParams(): array
146
    {
147 2
        return $this->cookieParams;
148
    }
149
150
    /**
151
     * {@inheritdoc}
152
     */
153 1
    public function withCookieParams(array $cookies): ServerRequestInterface
154
    {
155 1
        $clone = clone $this;
156 1
        $clone->cookieParams = $cookies;
157
158 1
        return $clone;
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164 2
    public function getQueryParams(): array
165
    {
166 2
        return $this->queryParams;
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172 1
    public function withQueryParams(array $query): ServerRequestInterface
173
    {
174 1
        $clone = clone $this;
175 1
        $clone->queryParams = $query;
176
177 1
        return $clone;
178
    }
179
180
    /**
181
     * {@inheritdoc}
182
     */
183 2
    public function getUploadedFiles(): array
184
    {
185 2
        return $this->uploadedFiles;
186
    }
187
188
    /**
189
     * {@inheritdoc}
190
     */
191 1
    public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface
192
    {
193 1
        $this->assertUploadedFiles($uploadedFiles);
194
195 1
        $clone = clone $this;
196 1
        $clone->uploadedFiles = $uploadedFiles;
197
198 1
        return $clone;
199
    }
200
201
    /**
202
     * {@inheritdoc}
203
     */
204 2
    public function getParsedBody()
205
    {
206 2
        return $this->parsedBody;
207
    }
208
209
    /**
210
     * {@inheritdoc}
211
     */
212 1
    public function withParsedBody($data): ServerRequestInterface
213
    {
214 1
        $this->assertParsedBody($data);
215
216 1
        $clone = clone $this;
217 1
        $clone->parsedBody = $data;
218
219 1
        return $clone;
220
    }
221
222
    /**
223
     * {@inheritdoc}
224
     */
225 1
    public function getAttributes(): array
226
    {
227 1
        return $this->attributes;
228
    }
229
230
    /**
231
     * {@inheritdoc}
232
     */
233 2
    public function getAttribute($name, $default = null)
234
    {
235 2
        return isset($this->attributes[$name]) ? $this->attributes[$name] : $default;
236
    }
237
238
    /**
239
     * {@inheritdoc}
240
     */
241 3
    public function withAttribute($name, $value): ServerRequestInterface
242
    {
243 3
        $clone = clone $this;
244 3
        $clone->attributes[$name] = $value;
245
246 3
        return $clone;
247
    }
248
249
    /**
250
     * {@inheritdoc}
251
     */
252 1
    public function withoutAttribute($name): ServerRequestInterface
253
    {
254 1
        $clone = clone $this;
255
256 1
        if (isset($this->attributes[$name])) {
257 1
            unset($clone->attributes[$name]);
258
        }
259
260 1
        return $clone;
261
    }
262
263
    /*
264
    |--------------------------------------------------------------------------
265
    | Non-PSR-7 Methods.
266
    |--------------------------------------------------------------------------
267
    */
268
269
    /**
270
     * Check out whether an array is compatible to PSR-7 file structure.
271
     * 
272
     * @param array $values The array to check.
273
     *
274
     * @return void
275
     *
276
     * @throws InvalidArgumentException
277
     */
278 2
    protected function assertUploadedFiles(array $values): void
279
    {
280 2
        foreach ($values as $value) {
281 2
            if (is_array($value)) {
282 1
                $this->assertUploadedFiles($value);
283 2
            } elseif (!($value instanceof UploadedFileInterface)) {
284 1
                throw new InvalidArgumentException(
285 1
                    'Invalid PSR-7 array structure for handling UploadedFile.'
286 1
                );
287
            }
288
        }
289
    }
290
291
    /**
292
     * Throw an exception if an unsupported argument type is provided.
293
     * 
294
     * @param string|array|null $data The deserialized body data. This will
295
     *     typically be in an array or object.
296
     *
297
     * @return void
298
     *
299
     * @throws InvalidArgumentException
300
     */
301 2
    protected function assertParsedBody($data): void
302
    {
303
        if (
304 2
            ! is_null($data) &&
305 2
            ! is_array($data) && 
306 2
            ! is_object($data)
307
        ) {
308 1
            throw new InvalidArgumentException(
309 1
                sprintf(
310 1
                    'Only accepts array, object and null, but "%s" provided.',
311 1
                    gettype($data)
312 1
                )
313 1
            );
314
        }
315
    }
316
317
    /**
318
     * Confirm the content type and post values whether fit the requirement.
319
     *
320
     * @param array $postParams 
321
     * @return void
322
     */
323 11
    protected function determineParsedBody(array $postParams)
324
    {
325 11
        $headerContentType = $this->getHeaderLine('Content-Type');
326 11
        $contentTypeArr = preg_split('/\s*[;,]\s*/', $headerContentType);
327 11
        $contentType = strtolower($contentTypeArr[0]);
328 11
        $httpMethod = strtoupper($this->getMethod());
329
330
        // Is it a form submit or not.
331 11
        $isForm = false;
332
333 11
        if ($httpMethod === 'POST') {
334
335
            // If the request Content-Type is either application/x-www-form-urlencoded
336
            // or multipart/form-data, and the request method is POST, this method MUST
337
            // return the contents of $_POST.
338 1
            $postRequiredContentTypes = [
339 1
                '', // For unit testing purpose.
340 1
                'application/x-www-form-urlencoded',
341 1
                'multipart/form-data',
342 1
            ];
343
344 1
            if (in_array($contentType, $postRequiredContentTypes)) {
345 1
                $this->parsedBody = $postParams ?? null;
346 1
                $isForm = true;
347
            }
348
        }
349
350
        // @codeCoverageIgnoreStart
351
        // Maybe other http methods such as PUT, DELETE, etc...
352
        if ($httpMethod !== 'GET' && !$isForm) {
353
354
            // If it a JSON formatted string?
355
            $isJson = false;
356
357
            // Receive content from PHP stdin input, if exists.
358
            $rawText = file_get_contents('php://input');
359
360
            if (!empty($rawText)) {
361
362
                if ($contentType === 'application/json') {
363
                    $jsonParsedBody = json_decode($rawText);
364
                    $isJson = (json_last_error() === JSON_ERROR_NONE);
365
                }
366
367
                // Condition 1 - It's a JSON, now the body is a JSON object.
368
                if ($isJson) {
369
                    $this->parsedBody = $jsonParsedBody ?: null;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $jsonParsedBody does not seem to be defined for all execution paths leading up to this point.
Loading history...
370
                }
371
372
                // Condition 2 - It's not a JSON, might be a http build query.
373
                if (!$isJson) {
374
                    parse_str($rawText, $parsedStr);
375
                    $this->parsedBody = $parsedStr ?: null;
376
                }
377
            }
378
        }
379
380
        // This part is manually tested by using PostMan.
381
        // @codeCoverageIgnoreEnd
382
    }
383
}
384