Completed
Push — master ( bcca1d...88e4a9 )
by Chris
03:02
created

Request::method()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

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