Completed
Push — master ( 2fef44...399242 )
by Chris
02:54
created

Request::flashes()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 13
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 22
rs 8.9197
1
<?php
2
namespace Darya\Http;
3
4
use Darya\Http\Session;
5
6
/**
7
 * Darya's HTTP request representation.
8
 * 
9
 * @property-read array $get
10
 * @property-read array $post
11
 * @property-read array $cookie
12
 * @property-read array $file
13
 * @property-read array $server
14
 * @property-read array $header
15
 * @method mixed get(string $key = null, mixed $default = null)
16
 * @method mixed post(string $key = null, mixed $default = null)
17
 * @method mixed cookie(string $key = null, mixed $default = null)
18
 * @method mixed file(string $key = null, mixed $default = null)
19
 * @method mixed server(string $key = null, mixed $default = null)
20
 * @method mixed header(string $key = null, mixed $default = null)
21
 * 
22
 * @author Chris Andrew <[email protected]>
23
 */
24
class Request
25
{
26
	/**
27
	 * Request data types to be treated case-insensitively.
28
	 * 
29
	 * @var array
30
	 */
31
	protected static $caseInsensitive = array('server', 'header');
32
	
33
	/**
34
	 * The request data.
35
	 * 
36
	 * @var array
37
	 */
38
	protected $data = array(
39
		'get'     => array(),
40
		'post'    => array(),
41
		'cookie'  => array(),
42
		'file'    => array(),
43
		'server'  => array(),
44
		'header'  => array(),
45
		'session' => null
46
	);
47
	
48
	/**
49
	 * Request body content.
50
	 * 
51
	 * @var string
52
	 */
53
	protected $content;
54
	
55
	/**
56
	 * The session that belongs to this request.
57
	 * 
58
	 * @var \Darya\Http\Session
59
	 */
60
	protected $session;
61
	
62
	/**
63
	 * The router that matched this request.
64
	 * 
65
	 * @var \Darya\Routing\Router
66
	 */
67
	public $router;
68
	
69
	/**
70
	 * The route that this request was matched with.
71
	 * 
72
	 * @var \Darya\Routing\Route
73
	 */
74
	public $route;
75
	
76
	/**
77
	 * Determine whether the given data type's keys can be treated
78
	 * case-insensitively.
79
	 * 
80
	 * @param string $type
81
	 * @return bool
82
	 */
83
	protected static function isCaseInsensitive($type)
84
	{
85
		return in_array($type, static::$caseInsensitive);
86
	}
87
	
88
	/**
89
	 * Prepare the given request data where necessary.
90
	 * 
91
	 * Lowercases data type keys and the keys of `server` and `header` data so
92
	 * they can be treated case-insensitively.
93
	 * 
94
	 * Any expected data types not satisfied will contain an empty array apart
95
	 * from `session`, which will be null.
96
	 * 
97
	 * @param array $data
98
	 * @return array
99
	 */
100
	protected static function prepareData(array $data)
101
	{
102
		$data = array_change_key_case($data);
103
		
104
		foreach (array_keys($data) as $type) {
105
			if (static::isCaseInsensitive($type)) {
106
				$data[$type] = array_change_key_case($data[$type]);
107
			}
108
		}
109
		
110
		return array_merge(array(
111
			'get'     => array(),
112
			'post'    => array(),
113
			'cookie'  => array(),
114
			'file'    => array(),
115
			'server'  => array(),
116
			'header'  => array(),
117
			'session' => null
118
		), $data);
119
	}
120
	
121
	
122
	/**
123
	 * Parse the given URI and return its components.
124
	 * 
125
	 * Any components not satisfied will be null instead of non-existent, so you
126
	 * can safely expect the keys 'scheme', 'host', 'port', 'user', 'pass',
127
	 * 'query' and 'fragment' to exist.
128
	 * 
129
	 * @param string $url
130
	 * @return array
131
	 */
132
	protected static function parseUrl($url)
133
	{
134
		$components = parse_url($url);
135
		
136
		return array_merge(array(
137
			'scheme' => null,
138
			'host'   => null,
139
			'port'   => null,
140
			'user'   => null,
141
			'pass'   => null,
142
			'path'   => null,
143
			'query'  => null,
144
			'fragment' => null
145
		), $components ?: array());
146
	}
147
	
148
	/**
149
	 * Parse the given query string and return its key value pairs.
150
	 * 
151
	 * @param string $query
152
	 * @return array
153
	 */
154
	protected static function parseQuery($query)
155
	{
156
		$values = array();
157
		parse_str($query, $values);
158
		
159
		return $values;
160
	}
161
	
162
	/**
163
	 * Create a new request with the given URL, method and data.
164
	 * 
165
	 * @param string  $url
166
	 * @param string  $method  [optional]
167
	 * @param array   $data    [optional]
168
	 * @param Session $session [optional]
169
	 * @return Request
170
	 */
171
	public static function create($url, $method = 'GET', $data = array(), Session $session = null)
172
	{
173
		$components = static::parseUrl($url);
174
		$data = static::prepareData($data);
175
		
176
		$data['get'] = array_merge(
177
			$data['get'],
178
			static::parseQuery($components['query'])
179
		);
180
		
181
		if ($components['host']) {
182
			$data['server']['http_host'] = $components['host'];
183
			$data['server']['server_name'] = $components['host'];
184
		}
185
		
186
		if ($components['path']) {
187
			$data['server']['path_info'] = $components['path'];
188
			$data['server']['request_uri'] = $components['path'];
189
		}
190
		
191
		$data['server']['request_method'] = strtoupper($method);
192
		
193
		if ($components['query']) {
194
			$data['server']['request_uri'] .= '?' . $components['query'];
195
		}
196
		
197
		$request = new Request(
198
			$data['get'],
199
			$data['post'],
200
			$data['cookie'],
201
			$data['file'],
202
			$data['server'],
203
			$data['header']
204
		);
205
		
206
		$request->setSession($session);
207
		
208
		return $request;
209
	}
210
	
211
	
212
	/**
213
	 * Extract HTTP request headers from a given set of $_SERVER globals.
214
	 * 
215
	 * @param array $server
216
	 * @return array
217
	 */
218
	public static function headersFromGlobals(array $server)
219
	{
220
		$headers = array();
221
		
222
		foreach ($server as $key => $value) {
223
			if (strpos($key, 'HTTP_') === 0) {
224
				$key = strtolower(substr($key, 5));
225
				$key = ucwords(str_replace('_', ' ', $key));
226
				$key = str_replace(' ', '-', $key);
227
				$headers[$key] = $value;
228
			}
229
		}
230
		
231
		return $headers;
232
	}
233
	
234
	/**
235
	 * Create a new request using PHP's super globals.
236
	 * 
237
	 * @param \Darya\Http\Session $session [optional]
238
	 * @return \Darya\Http\Request
239
	 */
240
	public static function createFromGlobals(Session $session = null)
241
	{
242
		$request = Request::create($_SERVER['REQUEST_URI'], $_SERVER['REQUEST_METHOD'], array(
243
			'get'    => $_GET,
244
			'post'   => $_POST,
245
			'cookie' => $_COOKIE,
246
			'file'   => $_FILES,
247
			'server' => $_SERVER,
248
			'header' => static::headersFromGlobals($_SERVER)
249
		), $session);
250
		
251
		return $request;
252
	}
253
	
254
	/**
255
	 * Instantiate a new request with the given data.
256
	 * 
257
	 * Expects request data in the same format as PHP superglobals.
258
	 * 
259
	 * @param array $get
260
	 * @param array $post
261
	 * @param array $cookie
262
	 * @param array $file
263
	 * @param array $server
264
	 * @param array $header
265
	 */
266
	public function __construct(array $get, array $post, array $cookie, array $file, array $server, array $header)
267
	{
268
		$this->data = static::prepareData(compact('get', 'post', 'cookie', 'file', 'server', 'header'));
269
	}
270
	
271
	/**
272
	 * Determine whether this Request has a session interface.
273
	 * 
274
	 * @return bool
275
	 */
276
	public function hasSession()
277
	{
278
		return !is_null($this->session);
279
	}
280
	
281
	/**
282
	 * Set the session interface for the request. Starts the session if it
283
	 * hasn't been already.
284
	 * 
285
	 * @param \Darya\Http\Session $session
286
	 */
287
	public function setSession(Session $session = null)
288
	{
289
		if (is_object($session) && !$session->started()) {
290
			$session->start();
291
		}
292
		
293
		$this->session = $session;
294
		$this->data['session'] = $this->session;
295
	}
296
	
297
	/**
298
	 * Retrieve request data of the given type using the given key.
299
	 * 
300
	 * If $key is not set, all request data of the given type will be returned.
301
	 * 
302
	 * If neither $type or $key are set, all request data will be returned.
303
	 * 
304
	 * If a $default value is given along with $key, it is returned if $key is
305
	 * not set in the data of the given $type.
306
	 * 
307
	 * @param string $type    [optional]
308
	 * @param string $key     [optional]
309
	 * @param mixed  $default [optional]
310
	 * @return mixed
311
	 */
312
	public function data($type = null, $key = null, $default = null)
313
	{
314
		$type = strtolower($type);
315
		
316
		if (isset($this->data[$type])) {
317
			if (static::isCaseInsensitive($type)) {
318
				$key = strtolower($key);
319
			}
320
			
321
			if (!empty($key)) {
322
				return isset($this->data[$type][$key]) ? $this->data[$type][$key] : $default;
323
			}
324
			
325
			return $this->data[$type];
326
		}
327
		
328
		return $this->data;
329
	}
330
	
331
	/**
332
	 * Dynamically retrieve all request data of the given type.
333
	 * 
334
	 * @param string $property
335
	 * @return array
336
	 */
337
	public function __get($property)
338
	{
339
		return $this->data($property);
340
	}
341
	
342
	/**
343
	 * Dynamically retrieve request data.
344
	 * 
345
	 * @param string $method
346
	 * @param array  $arguments
347
	 */
348
	public function __call($method, $arguments)
349
	{
350
		$arguments = array_merge(array($method), array_slice($arguments, 0, 2));
351
		
352
		return call_user_func_array(array($this, 'data'), $arguments);
353
	}
354
	
355
	/**
356
	 * Determine whether a given parameter is set in the request's post or get
357
	 * data.
358
	 * 
359
	 * @param string $key
360
	 * @return bool
361
	 */
362
	public function has($key)
363
	{
364
		return isset($this->data['get'][$key]) || isset($this->data['post'][$key]);
365
	}
366
	
367
	/**
368
	 * Retrieve a parameter from either the post or get data of the request,
369
	 * checking post data if the request method is post.
370
	 * 
371
	 * @param string $key
372
	 * @return mixed
373
	 */
374
	public function any($key = null)
375
	{
376
		return $this->method('post') && isset($this->data['post'][$key]) ? $this->post($key) : $this->get($key);
377
	}
378
	
379
	/**
380
	 * Retrieve the URI of the request.
381
	 * 
382
	 * @return string
383
	 */
384
	public function uri()
385
	{
386
		return $this->server('request_uri');
387
	}
388
	
389
	/**
390
	 * Retrieve the hostname of the request.
391
	 * 
392
	 * @return string
393
	 */
394
	public function host()
395
	{
396
		return $this->server('server_name') ?: $this->server('server_addr');
397
	}
398
	
399
	/**
400
	 * Retrieve the path of the request.
401
	 * 
402
	 * @return string
403
	 */
404
	public function path()
405
	{
406
		$path = $this->server('path_info');
407
		
408
		if ($path) {
409
			return $path;
410
		}
411
		
412
		$components = static::parseUrl($this->uri());
413
		
414
		return $components['path'];
415
	}
416
	
417
	/**
418
	 * Retrieve the method of the request or determine whether the method of the
419
	 * request is the same as the one given.
420
	 * 
421
	 * @param string $method [optional]
422
	 * @return string|bool
423
	 */
424
	public function method($method = null)
425
	{
426
		$method = strtolower($method);
427
		$requestMethod = strtolower($this->server('request_method'));
428
		
429
		return $method ? $requestMethod == $method : $this->server('request_method');
430
	}
431
	
432
	/**
433
	 * Retrieve the request body content.
434
	 * 
435
	 * @return string
436
	 */
437
	public function content()
438
	{
439
		if ($this->content === null) {
440
			$this->content = file_get_contents('php://input');
441
		}
442
		
443
		return $this->content;
444
	}
445
	
446
	/**
447
	 * Retrieve the IP address of the client that made the request.
448
	 * 
449
	 * @return string
450
	 */
451
	public function ip()
452
	{
453
		return $this->server('remote_addr');
454
	}
455
	
456
	/**
457
	 * Determine whether this is an ajax Request. This is determined by 'get' or
458
	 * 'post' data having an ajax parameter set or the 'X-Requested-With'
459
	 * parameter having the 'XMLHttpRequest' value.
460
	 * 
461
	 * @return bool
462
	 */
463
	public function ajax()
464
	{
465
		return $this->has('ajax')
466
		|| strtolower($this->server('http_x_requested_with')) == 'xmlhttprequest'
467
		|| strtolower($this->header('x-requested-with')) == 'xmlhttprequest';
468
	}
469
	
470
	/**
471
	 * Flash data with the given key to the session.
472
	 * 
473
	 * @param string       $key    Flash data key
474
	 * @param string|array $values A single value or set of values to add
475
	 * @return bool
476
	 */
477
	public function flash($key, $values)
478
	{
479
		if ($this->hasSession()) {
480
			$flash = $this->session->get('flash') ?: array();
481
			
482
			foreach ((array) $values as $value) {
483
				if (!is_null($value)) {
484
					$flash[$key][] = $value;
485
				}
486
			}
487
			
488
			$this->session->set('flash', $flash);
489
			
490
			return true;
491
		}
492
		
493
		return false;
494
	}
495
	
496
	/**
497
	 * Retrieve and clear flashed data with the given key from the session. If
498
	 * no key is given, all data is retrieved and cleared.
499
	 * 
500
	 * Returns an empty array if this request has no session or flash variables
501
	 * were not found with the given key.
502
	 * 
503
	 * @param string $key [optional] Flash data key
504
	 * @return array
505
	 */
506
	public function flashes($key = null)
507
	{
508
		$data = array();
509
		
510
		if ($this->hasSession()) {
511
			$flash = $this->session->get('flash');
512
			
513
			if (!empty($key)) {
514
				if (isset($flash[$key])) {
515
					$data = $flash[$key];
516
					unset($flash[$key]);
517
				}
518
			} else {
519
				$data = $flash;
520
				$flash = array();
521
			}
522
			
523
			$this->session->set('flash', $flash);
524
		}
525
		
526
		return $data;
527
	}
528
	
529
	/**
530
	 * Transforms post request data of the form entity[property][n] to the form
531
	 * entity[n][property].
532
	 * 
533
	 * @param string $key Entity key (post parameter name)
534
	 * @return array
535
	 */
536
	public function postObjectData($key = null)
537
	{
538
		$post = $this->post($key);
539
		$data = array();
540
		
541
		if (is_array($post)) {
542
			foreach ($post as $field => $keys) {
543
				foreach ($keys as $key => $value) {
544
					$data[$key][$field] = $value;
545
				}
546
			}
547
		}
548
		
549
		return $data;
550
	}
551
}
552