Completed
Push — master ( 5f4425...3d1cfa )
by Michael
17:13 queued 10:55
created

ServerRequest::hasContentType()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 10
ccs 6
cts 6
cp 1
rs 9.4285
cc 3
eloc 5
nc 3
nop 1
crap 3
1
<?php
2
3
/**
4
 * This file is part of the miBadger package.
5
 *
6
 * @author Michael Webbers <[email protected]>
7
 * @license http://opensource.org/licenses/Apache-2.0 Apache v2 License
8
 * @version 1.0.0
9
 */
10
11
namespace miBadger\Http;
12
13
use Psr\Http\Message\ServerRequestInterface;
14
use Psr\Http\Message\StreamInterface;
15
use Psr\Http\Message\UriInterface;
16
17
/**
18
 * The server request class
19
 *
20
 * @see http://www.php-fig.org/psr/psr-7/
21
 * @since 1.0.0
22
 */
23
class ServerRequest extends Request implements ServerRequestInterface
24
{
25
	/** @var array The server parameters. */
26
	private $serverParams;
27
28
	/** @var array The cookie parameters. */
29
	private $cookieParams;
30
31
	/** @var array The query parameters. */
32
	private $queryParams;
33
34
	/** @var array The post parameters. */
35
	private $postParams;
36
37
	/** @var array The files parameters. */
38
	private $filesParams;
39
40
	/** @var array The uploaded files. */
41
	private $uploadedFiles;
42
43
	/** @var null|array|object The parsed body. */
44
	private $parsedBody;
45
46
	/** @var array The attributes. */
47
	private $attributes;
48
49
	/**
50
	 * Construct a Request object with the given method, uri, version, headers & body.
51
	 *
52
	 * @global array $_SERVER The server parameters.
53
	 * @global array $_COOKIE The cookie parameters.
54
	 * @global array $_GET The query parameters.
55
	 * @global array $_POST The post parameters.
56
	 * @global array $_FILES The files parameters.
57
	 *
58
	 * @param string $method = ''
59
	 * @param UriInterface|null $uri = null
60
	 * @param string $version = self::DEFAULT_VERSION
61
	 * @param array $headers = []
62
	 * @param StreamInterface|null $body = null
63
	 */
64 19
	public function __construct($method = '', UriInterface $uri = null, $version = self::DEFAULT_VERSION, array $headers = [], StreamInterface $body = null)
0 ignored issues
show
Coding Style introduced by
__construct 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...
Coding Style introduced by
__construct 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...
Coding Style introduced by
__construct uses the super-global variable $_POST 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...
Coding Style introduced by
__construct uses the super-global variable $_FILES 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...
65
	{
66 19
		if ($body === null) {
67 19
			$body = new Stream(fopen('php://input', 'r'));
68 19
		}
69
70 19
		$this->serverParams = $_SERVER;
71 19
		$this->cookieParams = $_COOKIE;
72 19
		$this->queryParams = $this->initQueryParams($this->serverParams);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->initQueryParams($this->serverParams) can be null. However, the property $queryParams is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
73 19
		$this->postParams = $_POST;
74 19
		$this->filesParams = $_FILES;
75 19
		$this->uploadedFiles = $this->initUploadedFiles($this->filesParams);
76 19
		$this->attributes = [];
77
78 19
		parent::__construct($this->initMethod($method), $this->initUri($uri), $version, $this->initHeaders($headers), $body);
79 19
	}
80
81
	/**
82
	 * Initialize the method.
83
	 *
84
	 * @param string $method
85
	 * @return string the method.
86
	 */
87 19
	private function initMethod($method)
88
	{
89 19
		return $method === '' && isset($this->getServerParams()['REQUEST_METHOD']) ? $this->getServerParams()['REQUEST_METHOD'] : $method;
90
	}
91
92
	/**
93
	 * Initialize the URI.
94
	 *
95
	 * @param UriInterface|null $uri
96
	 * @return UriInterface the URI.
97
	 */
98 19
	private function initUri($uri)
99
	{
100 19
		if ($uri !== null) {
101 1
			return $uri;
102
		}
103
104 19
		$scheme = isset($this->getServerParams()['HTTPS']) ? 'https://' : 'http://';
105 19
		$host = isset($this->getServerParams()['HTTP_HOST']) ? $scheme . $this->getServerParams()['HTTP_HOST'] : '';
106 19
		$path = isset($this->getServerParams()['REQUEST_URI']) ? $this->getServerParams()['REQUEST_URI'] : '';
107
108 19
		return new URI($host . $path);
109
	}
110
111
	/**
112
	 * Initialize the headers.
113
	 *
114
	 * @param array $headers
115
	 * @return array the headers.
116
	 */
117 19
	private function initHeaders($headers)
118
	{
119 19
		return $headers ?: getallheaders();
120
	}
121
122
	/**
123
	 * Initialize the headers.
124
	 *
125
	 * @param string $uri
0 ignored issues
show
Bug introduced by
There is no parameter named $uri. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
126
	 * @return array the headers.
0 ignored issues
show
Documentation introduced by
Should the return type not be array|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
127
	 */
128 19
	private function initQueryParams($serverParams)
129
	{
130 19
		if (!isset($serverParams['REQUEST_URI']) || !($query = parse_url($serverParams['REQUEST_URI'], \PHP_URL_QUERY))) {
131 1
			return [];
132
		}
133
134 19
		parse_str($query, $result);
135
136 19
		return $result;
137
	}
138
139
	/**
140
	 * Initialize the uploaded files.
141
	 *
142
	 * @param array $files
143
	 * @return array the uploaded files.
144
	 */
145 19
	private function initUploadedFiles(array $files)
146
	{
147 19
		$result = [];
148
149 19
		foreach ($files as $key => $value) {
150 19
			$result[$key] = $this->parseUploadedFiles($value);
151 19
		}
152
153 19
		return $result;
154
	}
155
156
	/**
157
	 * Parse uploaded files.
158
	 *
159
	 * @param array $files
160
	 * @return UploadedFile|array uploaded files.
161
	 */
162 19
	private function parseUploadedFiles($files)
163
	{
164
		// Empty
165 19
		$first = reset($files);
166
167
		// Single
168 19
		if (!is_array($first)) {
169 19
			return $this->parseSingleUploadedFiles($files);
170
		}
171
172
		// Multiple
173 19
		if (count(array_filter(array_keys($first), 'is_string')) === 0) {
174 19
			return $this->parseMultipleUploadedFiles($files);
175
		}
176
177
		// Namespace
178 19
		return $this->initUploadedFiles($files);
179
	}
180
181
	/**
182
	 * Parse single uploaded file.
183
	 *
184
	 * @param array $file
185
	 * @return UploadedFile single uploaded file.
186
	 */
187 19
	private function parseSingleUploadedFiles(array $file)
188
	{
189 19
		return new UploadedFile($file['name'], $file['type'], $file['tmp_name'], $file['error'], $file['size']);
190
	}
191
192
	/**
193
	 * Parse multiple uploaded files.
194
	 *
195
	 * @param array $files
196
	 * @return UploadedFiles[] multiple uploaded files.
197
	 */
198 19
	private function parseMultipleUploadedFiles(array $files)
199
	{
200 19
		$count = count($files['name']);
201 19
		$result = [];
202
203 19
		for ($i = 0; $i < $count; $i++) {
204 19
			$result[] = new UploadedFile($files['name'][$i], $files['type'][$i], $files['tmp_name'][$i], $files['error'][$i], $files['size'][$i]);
205 19
		}
206
207 19
		return $result;
208
	}
209
210
	/**
211
	 * {@inheritdoc}
212
	 */
213 19
	public function getServerParams()
214
	{
215 19
		return $this->serverParams;
216
	}
217
218
	/**
219
	 * {@inheritdoc}
220
	 */
221 1
	public function getCookieParams()
222
	{
223 1
		return $this->cookieParams;
224
	}
225
226
	/**
227
	 * Set the cookie params.
228
	 *
229
	 * @param array $cookieParams
230
	 * @return $this
231
	 */
232 1
	private function setCookieParams(array $cookieParams)
233
	{
234 1
		$this->cookieParams = $cookieParams;
235
236 1
		return $this;
237
	}
238
239
	/**
240
	 * {@inheritdoc}
241
	 */
242 1
	public function withCookieParams(array $cookieParams)
243
	{
244 1
		$result = clone $this;
245
246 1
		return $result->setCookieParams($cookieParams);
247
	}
248
249
	/**
250
	 * {@inheritdoc}
251
	 */
252 2
	public function getQueryParams()
253
	{
254 2
		return $this->queryParams;
255
	}
256
257
	/**
258
	 * Set the query params.
259
	 *
260
	 * @param array $queryParams
261
	 * @return $this
262
	 */
263 1
	private function setQueryParams(array $queryParams)
264
	{
265 1
		$this->queryParams = $queryParams;
266
267 1
		return $this;
268
	}
269
270
	/**
271
	 * {@inheritdoc}
272
	 */
273 1
	public function withQueryParams(array $queryParams)
274
	{
275 1
		$result = clone $this;
276
277 1
		return $result->setQueryParams($queryParams);
278
	}
279
280
	/**
281
	 * {@inheritdoc}
282
	 */
283 2
	public function getUploadedFiles()
284
	{
285 2
		return $this->uploadedFiles;
286
	}
287
288
	/**
289
	 * Set the uploaded files.
290
	 *
291
	 * @param array $uploadedFiles
292
	 * @return $this
293
	 */
294 1
	private function setUploadedFiles(array $uploadedFiles)
295
	{
296 1
		$this->uploadedFiles = $uploadedFiles;
297
298 1
		return $this;
299
	}
300
301
	/**
302
	 * {@inheritdoc}
303
	 */
304 1
	public function withUploadedFiles(array $uploadedFiles)
305
	{
306 1
		$result = clone $this;
307
308 1
		return $result->setUploadedFiles($uploadedFiles);
309
	}
310
311
	/**
312
	 * {@inheritdoc}
313
	 */
314 6
	public function getParsedBody()
315
	{
316 6
		if ($this->parsedBody !== null) {
317 1
			return $this->parsedBody;
318
		}
319
320 5
		if ($this->getMethod() === 'POST' && ($this->hasContentType('application/x-www-form-urlencoded') || $this->hasContentType('multipart/form-data'))) {
321 3
			return $this->postParams;
322
		}
323
324 2
		if ($this->hasContentType('application/json')) {
325 1
			return json_decode((string) $this->getBody());
326
		}
327
328 1
		return null;
329
	}
330
331
	/**
332
	 * Checks if a content type header exists with the given content type.
333
	 *
334
	 * @param string $contentType
335
	 * @return true if a content type header exists with the given content type.
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
336
	 */
337 5
	private function hasContentType($contentType)
338
	{
339 5
		foreach ($this->getHeader('Content-Type') as $key => $value) {
340 4
			if (substr($value, 0, strlen($contentType)) == $contentType) {
341 4
				return true;
342
			}
343 2
		}
344
345 2
		return false;
346
	}
347
348
	/**
349
	 * Set the parsed body.
350
	 *
351
	 * @param null|array|object $parsedBody
352
	 * @return $this
353
	 */
354 1
	private function setParsedBody($parsedBody)
355
	{
356 1
		$this->parsedBody = $parsedBody;
357
358 1
		return $this;
359
	}
360
361
	/**
362
	 * {@inheritdoc}
363
	 */
364 1
	public function withParsedBody($parsedBody)
365
	{
366 1
		$result = clone $this;
367
368 1
		return $result->setParsedBody($parsedBody);
369
	}
370
371
	/**
372
	 * {@inheritdoc}
373
	 */
374 1
	public function getAttributes()
375
	{
376 1
		return $this->attributes;
377
	}
378
379
	/**
380
	 * {@inheritdoc}
381
	 */
382 3
	public function getAttribute($name, $default = null)
383
	{
384 3
		return isset($this->attributes[$name]) ? $this->attributes[$name] : $default;
385
	}
386
387
	/**
388
	 * Set the attribute.
389
	 *
390
	 * @param string $name
391
	 * @param mixed $value
392
	 * @return $this
393
	 */
394 1
	private function setAttribute($name, $value)
395
	{
396 1
		$this->attributes[$name] = $value;
397
398 1
		return $this;
399
	}
400
401
	/**
402
	 * {@inheritdoc}
403
	 */
404 1
	public function withAttribute($name, $value)
405
	{
406 1
		$result = clone $this;
407
408 1
		return $result->setAttribute($name, $value);
409
	}
410
411
	/**
412
	 * Remove the attribute.
413
	 *
414
	 * @param string $name
415
	 * @return $this
416
	 */
417 1
	private function removeAttribute($name)
418
	{
419 1
		unset($this->attributes[$name]);
420
421 1
		return $this;
422
	}
423
424
	/**
425
	 * {@inheritdoc}
426
	 */
427 1
	public function withoutAttribute($name)
428
	{
429 1
		$result = clone $this;
430
431 1
		return $result->removeAttribute($name);
432
	}
433
}
434