Completed
Push — master ( 6b8416...c520c0 )
by Filipe
02:12
created

Request   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 353
Duplicated Lines 0 %

Coupling/Cohesion

Components 7
Dependencies 6

Importance

Changes 0
Metric Value
wmc 36
lcom 7
cbo 6
dl 0
loc 353
rs 7.3333
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 3
A getServerParams() 0 7 2
A getCookieParams() 0 7 2
A withCookieParams() 0 6 1
A getQueryParams() 0 7 2
A withQueryParams() 0 6 1
A getUploadedFiles() 0 7 2
A withUploadedFiles() 0 13 2
A detectQueryParams() 0 6 1
A getPhpInputStream() 0 5 1
A checkUploadedFiles() 0 18 4
A loadHeaders() 0 16 3
A getParsedBody() 0 8 2
A withParsedBody() 0 17 4
A getAttributes() 0 4 1
A withAttribute() 0 6 1
A getAttribute() 0 7 2
A withoutAttribute() 0 8 2
1
<?php
2
3
/**
4
 * This file is part of slick/http
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
namespace Slick\Http\Message\Server;
11
12
use Psr\Http\Message\ServerRequestInterface;
13
use Psr\Http\Message\StreamInterface;
14
use Psr\Http\Message\UriInterface;
15
use Slick\Http\Message\Exception\InvalidArgumentException;
16
use Slick\Http\Message\Request as HttpRequest;
17
use Slick\Http\Message\Stream\TextStream;
18
use Slick\Http\Message\Uri;
19
20
/**
21
 * Request
22
 *
23
 * @package Slick\Http\Message\Server
24
*/
25
class Request extends HttpRequest implements ServerRequestInterface
26
{
27
28
    /**
29
     * @var array
30
     */
31
    private $server;
32
33
    /**
34
     * @var array
35
     */
36
    private $cookies;
37
38
    /**
39
     * @var array
40
     */
41
    private $queryParams;
42
43
    /**
44
     * @var UploadedFile[]
45
     */
46
    private $uploadedFiles;
47
48
    /**
49
     * @var mixed
50
     */
51
    private $parsedBody;
52
53
    /**
54
     * @var array
55
     */
56
    private $attributes = [];
57
58
    /**
59
     * Creates an HTTP Server Request Message
60
     *
61
     * @param string                   $method
62
     * @param string|StreamInterface   $body
63
     * @param null|string|UriInterface $target
64
     * @param array                    $headers
65
     */
66
    public function __construct($method = null, $target = null, $body = '', array $headers = [])
67
    {
68
        $method = null === $method
69
            ? $this->getServerParams()['REQUEST_METHOD']
70
            : $method;
71
72
        $body = null === $body
73
            ? $this->getPhpInputStream()
74
            : $body;
75
76
        parent::__construct($method, $target, $body, $headers);
77
        $this->loadHeaders();
78
    }
79
80
    /**
81
     * Retrieve server parameters.
82
     *
83
     * Retrieves data related to the incoming request environment,
84
     * typically derived from PHP's $_SERVER superglobal.
85
     *
86
     * @return array
87
     */
88
    public function getServerParams()
0 ignored issues
show
Coding Style introduced by
getServerParams uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
89
    {
90
        if (! $this->server) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->server of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
91
            $this->server = $_SERVER;
92
        }
93
        return $this->server;
94
    }
95
96
    /**
97
     * Retrieve cookies.
98
     *
99
     * Retrieves cookies sent by the client to the server.
100
     *
101
     * @return array
102
     */
103
    public function getCookieParams()
0 ignored issues
show
Coding Style introduced by
getCookieParams uses the super-global variable $_COOKIE which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
104
    {
105
        if (! $this->cookies) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->cookies of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
106
            $this->cookies = $_COOKIE;
107
        }
108
        return $this->cookies;
109
    }
110
111
    /**
112
     * Return an instance with the specified cookies.
113
     *
114
     * @param array $cookies Array of key/value pairs representing cookies.
115
     * @return Request
116
     */
117
    public function withCookieParams(array $cookies)
118
    {
119
        $request = clone $this;
120
        $request->cookies = $cookies;
121
        return $request;
122
    }
123
124
    /**
125
     * Retrieve query string arguments.
126
     *
127
     * Retrieves the deserialized query string arguments, if any.
128
     *
129
     * @return array
130
     */
131
    public function getQueryParams()
132
    {
133
        if (! $this->queryParams) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->queryParams of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
134
            $this->queryParams = $this->detectQueryParams();
135
        }
136
        return $this->queryParams;
137
    }
138
139
    /**
140
     * Return an instance with the specified query string arguments.
141
     *
142
     * @param array $query Array of query string arguments, typically from
143
     *     $_GET.
144
     *
145
     * @return Request
146
     */
147
    public function withQueryParams(array $query)
148
    {
149
        $request = clone $this;
150
        $request->queryParams = $query;
151
        returN $request;
152
    }
153
154
    /**
155
     * Retrieve normalized file upload data.
156
     *
157
     * @return UploadedFile[]
158
     */
159
    public function getUploadedFiles()
160
    {
161
        if (null === $this->uploadedFiles) {
162
            $this->uploadedFiles = UploadedFilesFactory::createFiles();
163
        }
164
        return $this->uploadedFiles;
165
    }
166
167
    /**
168
     * Create a new instance with the specified uploaded files.
169
     *
170
     * @param array $uploadedFiles An array tree of UploadedFileInterface instances.
171
     * @return Request
172
     *
173
     * @throws InvalidArgumentException if an invalid structure is provided.
174
     */
175
    public function withUploadedFiles(array $uploadedFiles)
176
    {
177
        if (! $this->checkUploadedFiles($uploadedFiles)) {
178
            throw new InvalidArgumentException(
179
                "The uploaded files array given has at least one leaf that is not ".
180
                "an UploadedFile object."
181
            );
182
        }
183
184
        $request = clone $this;
185
        $request->uploadedFiles = $uploadedFiles;
186
        return $request;
187
    }
188
189
190
    /**
191
     * Detects the query params from server and/or request URI
192
     *
193
     * @return array
194
     */
195
    private function detectQueryParams()
0 ignored issues
show
Coding Style introduced by
detectQueryParams uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
196
    {
197
        $uri = new Uri('http://example.org'.$this->getRequestTarget());
198
        parse_str($uri->getQuery(), $params);
199
        return array_merge($_GET, $params);
200
    }
201
202
    /**
203
     * Creates a stream from php input stream
204
     *
205
     * @return StreamInterface
206
     */
207
    private function getPhpInputStream()
208
    {
209
        $stream = new TextStream(file_get_contents('php://input'));
210
        return $stream;
211
    }
212
213
    /**
214
     * Check if provided files array is valid
215
     *
216
     * @param array $files
217
     *
218
     * @return bool
219
     */
220
    private function checkUploadedFiles(array $files)
221
    {
222
        $valid = true;
223
224
        foreach ($files as $file) {
225
            if (is_array($file)) {
226
                $valid = $this->checkUploadedFiles($files);
227
                break;
228
            }
229
230
            if (! $file instanceof UploadedFile) {
231
                $valid = false;
232
                break;
233
            }
234
        }
235
236
        return $valid;
237
    }
238
239
    /**
240
     * Loads the headers form request
241
     */
242
    private function loadHeaders()
0 ignored issues
show
Coding Style introduced by
loadHeaders uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
243
    {
244
        foreach($_SERVER as $key => $value) {
245
            if (substr($key, 0, 5) <> 'HTTP_') {
246
                continue;
247
            }
248
            $header = str_replace(
249
                ' ',
250
                '-',
251
                ucwords(
252
                    str_replace('_', ' ', strtolower(substr($key, 5)))
253
                )
254
            );
255
            $this->headers[$this->headerKey($header)] = [$value];
256
        }
257
    }
258
259
    /**
260
     * Retrieve any parameters provided in the request body.
261
     *
262
     * @return null|array|object The deserialized body parameters, if any.
263
     *     These will typically be an array or object.
264
     */
265
    public function getParsedBody()
266
    {
267
        if (! $this->parsedBody) {
268
            $parser = new BodyParser($this->getHeaderLine('Content-Type'));
269
            $this->parsedBody = $parser->parse($this->getBody());
270
        }
271
        return $this->parsedBody;
272
    }
273
274
    /**
275
     * Return an instance with the specified body parameters.
276
     *
277
     * @param null|array|object $data The deserialized body data. This will
278
     *     typically be in an array or object.
279
     *
280
     * @return Request
281
     * @throws InvalidArgumentException if an unsupported argument type is
282
     *     provided.
283
     */
284
    public function withParsedBody($data)
285
    {
286
        if (
287
            ! is_null($data) &&
288
            ! is_array($data) &&
289
            ! is_object($data)
290
        ) {
291
            throw new InvalidArgumentException(
292
                "Only NULL, array or Object types could be used to ".
293
                "create a new server request message with parsed body."
294
            );
295
        }
296
297
        $request = clone $this;
298
        $request->parsedBody = $data;
299
        return $request;
300
    }
301
302
    /**
303
     * Retrieve attributes derived from the request.
304
     *
305
     * The request "attributes" may be used to allow injection of any
306
     * parameters derived from the request: e.g., the results of path
307
     * match operations; the results of decrypting cookies; the results of
308
     * deserializing non-form-encoded message bodies; etc. Attributes
309
     * will be application and request specific, and CAN be mutable.
310
     *
311
     * @return mixed[] Attributes derived from the request.
312
     */
313
    public function getAttributes()
314
    {
315
        return $this->attributes;
316
    }
317
318
    /**
319
     * Return an instance with the specified derived request attribute.
320
     *
321
     * This method allows setting a single derived request attribute as
322
     * described in getAttributes().
323
     *
324
     * @see getAttributes()
325
     * @param string $name The attribute name.
326
     * @param mixed $value The value of the attribute.
327
     *
328
     * @return Request
329
     */
330
    public function withAttribute($name, $value)
331
    {
332
        $request = clone $this;
333
        $request->attributes[$name] = $value;
334
        return $request;
335
    }
336
337
    /**
338
     * Retrieve a single derived request attribute.
339
     *
340
     * Retrieves a single derived request attribute as described in
341
     * getAttributes(). If the attribute has not been previously set, returns
342
     * the default value as provided.
343
     *
344
     * @see getAttributes()
345
     * @param string $name The attribute name.
346
     * @param mixed $default Default value to return if the attribute does not exist.
347
     * @return mixed
348
     */
349
    public function getAttribute($name, $default = null)
350
    {
351
        if (array_key_exists($name, $this->attributes)) {
352
            $default = $this->attributes[$name];
353
        }
354
        return $default;
355
    }
356
357
    /**
358
     * Return an instance that removes the specified derived request attribute.
359
     *
360
     * This method allows removing a single derived request attribute as
361
     * described in getAttributes().
362
     *
363
     * @see getAttributes()
364
     * @param string $name The attribute name.
365
     *
366
     * @return Request
367
     */
368
    public function withoutAttribute($name)
369
    {
370
        $request = clone $this;
371
        if (array_key_exists($name, $request->attributes)) {
372
            unset($request->attributes[$name]);
373
        }
374
        return $request;
375
    }
376
377
}