Completed
Push — master ( 9c73ab...3bd5c7 )
by Timothy
02:45
created

Dispatcher::process_route()   C

Complexity

Conditions 8
Paths 17

Size

Total Lines 43
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 43
rs 5.3846
cc 8
eloc 21
nc 17
nop 1
1
<?php
2
/**
3
 * Hummingbird Anime Client
4
 *
5
 * An API client for Hummingbird to manage anime and manga watch lists
6
 *
7
 * @package     HummingbirdAnimeClient
8
 * @author      Timothy J. Warren
9
 * @copyright   Copyright (c) 2015 - 2016
10
 * @link        https://github.com/timw4mail/HummingBirdAnimeClient
11
 * @license     MIT
12
 */
13
namespace Aviat\AnimeClient;
14
15
use Aura\Web\Request;
16
use Aura\Web\Response;
17
18
use Aviat\Ion\Di\ContainerInterface;
19
use Aviat\AnimeClient\AnimeClient;
20
21
/**
22
 * Basic routing/ dispatch
23
 */
24
class Dispatcher extends RoutingBase {
25
26
	/**
27
	 * The route-matching object
28
	 * @var object $router
29
	 */
30
	protected $router;
31
32
	/**
33
	 * Class wrapper for input superglobals
34
	 * @var object
35
	 */
36
	protected $request;
37
38
	/**
39
	 * Routes added to router
40
	 * @var array $output_routes
41
	 */
42
	protected $output_routes;
43
44
	/**
45
	 * Constructor
46
	 *
47
	 * @param ContainerInterface $container
48
	 */
49
	public function __construct(ContainerInterface $container)
50
	{
51
		parent::__construct($container);
52
		$this->router = $container->get('aura-router');
53
		$this->request = $container->get('request');
54
55
		$this->output_routes = $this->_setup_routes();
56
	}
57
58
	/**
59
	 * Get the current route object, if one matches
60
	 *
61
	 * @return object
62
	 */
63
	public function get_route()
64
	{
65
		$error_handler = $this->container->get('error-handler');
66
67
		$raw_route = $this->request->url->get(PHP_URL_PATH);
68
		$route_path = "/" . trim($raw_route, '/');
69
70
		$error_handler->addDataTable('Route Info', [
71
			'route_path' => $route_path
72
		]);
73
74
		return $this->router->match($route_path, $_SERVER);
75
	}
76
77
	/**
78
	 * Get list of routes applied
79
	 *
80
	 * @return array
81
	 */
82
	public function get_output_routes()
83
	{
84
		return $this->output_routes;
85
	}
86
87
	/**
88
	 * Handle the current route
89
	 *
90
	 * @codeCoverageIgnore
91
	 * @param object|null $route
92
	 * @return void
93
	 */
94
	public function __invoke($route = NULL)
95
	{
96
		$error_handler = $this->container->get('error-handler');
97
98
		if (is_null($route))
99
		{
100
			$route = $this->get_route();
101
			$error_handler->addDataTable('route_args', (array)$route);
102
		}
103
104
		if($route)
105
		{
106
			$parsed = $this->process_route($route);
107
			$controller_name = $parsed['controller_name'];
108
			$action_method = $parsed['action_method'];
109
			$params = $parsed['params'];
110
		}
111
		else
112
		{
113
			// If not route was matched, return an appropriate http
114
			// error message
115
			$error_route = $this->get_error_params();
116
			$controller_name = AnimeClient::DEFAULT_CONTROLLER;
117
			$action_method = $error_route['action_method'];
118
			$params = $error_route['params'];
119
		}
120
121
		// Actually instantiate the controller
122
		$this->call($controller_name, $action_method, $params);
123
	}
124
125
	/**
126
	 * Parse out the arguments for the appropriate controller for
127
	 * the current route
128
	 *
129
	 * @param \Aura\Router\Route $route
130
	 * @return array
131
	 */
132
	protected function process_route($route)
133
	{
134
		if (array_key_exists('controller', $route->params))
135
		{
136
			$controller_name = $route->params['controller'];
137
		}
138
		else
139
		{
140
			throw new \LogicException("Missing controller");
141
		}
142
143
		// Get the full namespace for a controller if a short name is given
144
		if (strpos($controller_name, '\\') === FALSE)
145
		{
146
			$map = $this->get_controller_list();
147
			$controller_name = $map[$controller_name];
148
		}
149
150
		$action_method = (array_key_exists('action', $route->params))
151
			? $route->params['action']
152
			: AnimeClient::NOT_FOUND_METHOD;
153
154
		$params = (array_key_exists('params', $route->params))
155
			? $route->params['params']
156
			: [];
157
158
		if ( ! empty($route->tokens))
159
		{
160
			foreach ($route->tokens as $key => $v)
161
			{
162
				if (array_key_exists($key, $route->params))
163
				{
164
					$params[$key] = $route->params[$key];
165
				}
166
			}
167
		}
168
169
		return [
170
			'controller_name' => $controller_name,
171
			'action_method' => $action_method,
172
			'params' => $params
173
		];
174
	}
175
176
	/**
177
	 * Get the type of route, to select the current controller
178
	 *
179
	 * @return string
180
	 */
181
	public function get_controller()
182
	{
183
		$route_type = $this->__get('default_list');
184
		$request_uri = $this->request->url->get(PHP_URL_PATH);
185
		$path = trim($request_uri, '/');
186
187
		$segments = explode('/', $path);
188
		$controller = reset($segments);
189
190
		if (empty($controller))
191
		{
192
			$controller = $route_type;
193
		}
194
195
		return $controller;
196
	}
197
198
	/**
199
	 * Get the list of controllers in the default namespace
200
	 *
201
	 * @return array
202
	 */
203
	public function get_controller_list()
204
	{
205
		$default_namespace = AnimeClient::DEFAULT_CONTROLLER_NAMESPACE;
206
		$path = str_replace('\\', '/', $default_namespace);
207
		$path = trim($path, '/');
208
		$actual_path = realpath(\_dir(AnimeClient::SRC_DIR, $path));
209
		$class_files = glob("{$actual_path}/*.php");
210
211
		$controllers = [];
212
213
		foreach ($class_files as $file)
214
		{
215
			$raw_class_name = basename(str_replace(".php", "", $file));
216
			$path = strtolower(basename($raw_class_name));
217
			$class_name = trim($default_namespace . '\\' . $raw_class_name, '\\');
218
219
			$controllers[$path] = $class_name;
220
		}
221
222
		return $controllers;
223
	}
224
225
	/**
226
	 * Create the controller object and call the appropriate
227
	 * method
228
	 *
229
	 * @param  string $controller_name - The full namespace of the controller class
230
	 * @param  string $method
231
	 * @param  array  $params
232
	 * @return void
233
	 */
234
	protected function call($controller_name, $method, array $params)
235
	{
236
		$error_handler = $this->container->get('error-handler');
237
238
		$controller = new $controller_name($this->container);
239
240
		// Run the appropriate controller method
241
		$error_handler->addDataTable('controller_args', $params);
242
		call_user_func_array([$controller, $method], $params);
243
	}
244
245
	/**
246
	 * Get the appropriate params for the error page
247
	 * pased on the failed route
248
	 *
249
	 * @return array|false
250
	 */
251
	protected function get_error_params()
252
	{
253
		$failure = $this->router->getFailedRoute();
254
		$error_handler = $this->container->get('error-handler');
255
		$error_handler->addDataTable('failed_route', (array)$failure);
256
		$action_method = AnimeClient::ERROR_MESSAGE_METHOD;
257
258
		$params = [];
259
260
		if ($failure->failedMethod())
261
		{
262
			$params = [
263
				'http_code' => 405,
264
				'title' => '405 Method Not Allowed',
265
				'message' => 'Invalid HTTP Verb'
266
			];
267
		}
268
		else if($failure->failedAccept())
269
		{
270
			$params = [
271
				'http_code' => 406,
272
				'title' => '406 Not Acceptable',
273
				'message' => 'Unacceptable content type'
274
			];
275
		}
276
		else
277
		{
278
			// Fall back to a 404 message
279
			$action_method = AnimeClient::NOT_FOUND_METHOD;
280
		}
281
282
		return [
283
			'params' => $params,
284
			'action_method' => $action_method
285
		];
286
	}
287
288
	/**
289
	 * Select controller based on the current url, and apply its relevent routes
290
	 *
291
	 * @return array
292
	 */
293
	protected function _setup_routes()
294
	{
295
		$route_type = $this->get_controller();
296
297
		// Add routes
298
		$routes = [];
299
		foreach ($this->routes as $name => &$route)
300
		{
301
			$path = $route['path'];
302
			unset($route['path']);
303
304
			$controller_map = $this->get_controller_list();
305
			$controller_class = (array_key_exists($route_type, $controller_map))
306
				? $controller_map[$route_type]
307
				: AnimeClient::DEFAULT_CONTROLLER;
308
309
			if (array_key_exists($route_type, $controller_map))
310
			{
311
				$controller_class = $controller_map[$route_type];
312
			}
313
314
			// Prepend the controller to the route parameters
315
			$route['controller'] = $controller_class;
316
317
			// Select the appropriate router method based on the http verb
318
			$add = (array_key_exists('verb', $route))
319
				? "add" . ucfirst(strtolower($route['verb']))
320
				: "addGet";
321
322
			// Add the route to the router object
323
			if ( ! array_key_exists('tokens', $route))
324
			{
325
				$routes[] = $this->router->$add($name, $path)->addValues($route);
326
			}
327
			else
328
			{
329
				$tokens = $route['tokens'];
330
				unset($route['tokens']);
331
332
				$routes[] = $this->router->$add($name, $path)
333
					->addValues($route)
334
					->addTokens($tokens);
335
			}
336
		}
337
338
		return $routes;
339
	}
340
}
341
// End of Dispatcher.php