Completed
Push — master ( 125148...6e4e8e )
by Timothy
16:08
created

Dispatcher::get_controller()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 17
rs 9.4286
cc 2
eloc 9
nc 2
nop 0
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
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
20
/**
21
 * Basic routing/ dispatch
22
 */
23
class Dispatcher extends RoutingBase {
24
25
	/**
26
	 * The route-matching object
27
	 * @var object $router
28
	 */
29
	protected $router;
30
31
	/**
32
	 * Class wrapper for input superglobals
33
	 * @var object
34
	 */
35
	protected $request;
36
37
	/**
38
	 * Routes added to router
39
	 * @var array $output_routes
40
	 */
41
	protected $output_routes;
42
43
	/**
44
	 * Constructor
45
	 *
46
	 * @param ContainerInterface $container
47
	 */
48
	public function __construct(ContainerInterface $container)
49
	{
50
		parent::__construct($container);
51
		$this->router = $container->get('aura-router');
52
		$this->request = $container->get('request');
53
54
		$this->output_routes = $this->_setup_routes();
55
		$this->generate_convention_routes();
56
	}
57
58
	/**
59
	 * Generate routes based on controller methods
60
	 *
61
	 * @return void
62
	 */
63
	protected function generate_convention_routes()
64
	{
65
		$default_controller = $this->routes['convention']['default_controller'];
66
67
		$this->output_routes[] = $this->router->addGet('login', '/{controller}/login')
68
			->setValues([
69
				'controller' => $default_controller,
70
				'action' => 'login'
71
			]);
72
73
		$this->output_routes[] = $this->router->addPost('login_post', '/{controller}/login')
74
			->setValues([
75
				'controller' => $default_controller,
76
				'action' => 'login_action'
77
			]);
78
79
		$this->output_routes[] = $this->router->addGet('logout', '/{controller}/logout')
80
			->setValues([
81
				'controller' => $default_controller,
82
				'action' => 'logout'
83
			]);
84
85
		$this->output_routes[] = $this->router->addPost('update', '/{controller}/update')
86
			->setValues([
87
				'controller' => $default_controller,
88
				'action' => 'update'
89
			])->setTokens([
90
				'controller' => '[a-z_]+'
91
			]);
92
93
		$this->output_routes[] = $this->router->addPost('update_form', '/{controller}/update_form')
94
			->setValues([
95
				'controller' => $default_controller,
96
				'action' => 'form_update'
97
			])->setTokens([
98
				'controller' => '[a-z_]+'
99
			]);
100
101
		$this->output_routes[] = $this->router->addGet('edit', '/{controller}/edit/{id}/{status}')
102
			->setValues([
103
				'controller' => $default_controller,
104
				'action' => 'edit'
105
			])->setTokens([
106
				'id' => '[0-9a-z_]+',
107
				'status' => '[a-z\-]+',
108
			]);
109
110
		$this->output_routes[] = $this->router->addGet('list', '/{controller}/{type}{/view}')
111
			->setValues([
112
				'controller' => $default_controller,
113
				'action' => $this->routes['convention']['default_method'],
114
			])->setTokens([
115
				'type' => '[a-z_]+',
116
				'view' => '[a-z_]+'
117
			]);
118
119
		$this->output_routes[] = $this->router->addGet('index_redirect', '/')
120
			->setValues([
121
				'controller' => 'Aviat\\AnimeClient\\Controller',
122
				'action' => 'redirect_to_default'
123
			]);
124
	}
125
126
	/**
127
	 * Get the current route object, if one matches
128
	 *
129
	 * @return object
130
	 */
131
	public function get_route()
132
	{
133
		$error_handler = $this->container->get('error-handler');
134
135
		$raw_route = $this->request->server->get('PATH_INFO');
136
		$route_path = "/" . trim($raw_route, '/');
137
138
		$error_handler->addDataTable('Route Info', [
139
			'route_path' => $route_path
140
		]);
141
142
		return $this->router->match($route_path, $_SERVER);
143
	}
144
145
	/**
146
	 * Get list of routes applied
147
	 *
148
	 * @return array
149
	 */
150
	public function get_output_routes()
151
	{
152
		return $this->output_routes;
153
	}
154
155
	/**
156
	 * Handle the current route
157
	 *
158
	 * @codeCoverageIgnore
159
	 * @param object|null $route
160
	 * @return void
161
	 */
162
	public function __invoke($route = NULL)
163
	{
164
		$error_handler = $this->container->get('error-handler');
165
		$controller_name = $this->routes['convention']['default_controller'];
166
		$action_method = $this->routes['convention']['404_method'];
167
		$params = [];
168
169
		if (is_null($route))
170
		{
171
			$route = $this->get_route();
172
			$error_handler->addDataTable('route_args', (array)$route);
173
		}
174
175
		if ( ! $route)
176
		{
177
			$failure = $this->router->getFailedRoute();
178
			$error_handler->addDataTable('failed_route', (array)$failure);
179
		}
180
		else
181
		{
182
			if (isset($route->params['controller']))
183
			{
184
				$controller_name = $route->params['controller'];
185
			}
186
187
			if (isset($route->params['action']))
188
			{
189
				$action_method = $route->params['action'];
190
			}
191
192
			if (is_null($controller_name))
193
			{
194
				throw new \LogicException("Missing controller");
195
			}
196
197
			if (strpos($controller_name, '\\') === FALSE)
198
			{
199
				$map = $this->get_controller_list();
200
				$controller_name = $map[$controller_name];
201
			}
202
203
			$params = (isset($route->params['params'])) ? $route->params['params'] : [];
204
205
			if ( ! empty($route->tokens))
206
			{
207
				foreach ($route->tokens as $key => $v)
208
				{
209
					if (array_key_exists($key, $route->params))
210
					{
211
						$params[$key] = $route->params[$key];
212
					}
213
				}
214
			}
215
		}
216
217
		$controller = new $controller_name($this->container);
218
219
		// Run the appropriate controller method
220
		$error_handler->addDataTable('controller_args', $params);
221
		call_user_func_array([$controller, $action_method], $params);
222
	}
223
224
	/**
225
	 * Get the type of route, to select the current controller
226
	 *
227
	 * @return string
228
	 */
229
	public function get_controller()
230
	{
231
		$route_type = $this->__get('default_list');
232
		$request_uri = $this->request->server->get('PATH_INFO');
233
234
		$path = trim($request_uri, '/');
235
236
		$segments = explode('/', $path);
237
		$controller = reset($segments);
238
239
		if (empty($controller))
240
		{
241
			$controller = $route_type;
242
		}
243
244
		return $controller;
245
	}
246
247
	/**
248
	 * Get the list of controllers in the default namespace
249
	 *
250
	 * @return array
251
	 */
252
	public function get_controller_list()
253
	{
254
		$convention_routing = $this->routes['convention'];
255
		$default_namespace = $convention_routing['default_namespace'];
256
		$path = str_replace('\\', '/', $default_namespace);
257
		$path = trim($path, '/');
258
		$actual_path = \_dir(SRC_DIR, $path);
259
260
		$class_files = glob("{$actual_path}/*.php");
261
262
		$controllers = [];
263
264
		foreach ($class_files as $file)
265
		{
266
			$raw_class_name = basename(str_replace(".php", "", $file));
267
			$path = strtolower(basename($raw_class_name));
268
			$class_name = trim($default_namespace . '\\' . $raw_class_name, '\\');
269
270
			$controllers[$path] = $class_name;
271
		}
272
273
		return $controllers;
274
	}
275
276
	/**
277
	 * Select controller based on the current url, and apply its relevent routes
278
	 *
279
	 * @return array
280
	 */
281
	public function _setup_routes()
282
	{
283
		$routes = [];
284
285
		$route_type = $this->get_controller();
286
287
		// Return early if invalid route array
288
		if ( ! array_key_exists($route_type, $this->routes))
289
		{
290
			return [];
291
		}
292
293
		$applied_routes = $this->routes[$route_type];
294
295
		// Add routes
296
		foreach ($applied_routes as $name => &$route)
297
		{
298
			$path = $route['path'];
299
			unset($route['path']);
300
301
			$controller_map = $this->get_controller_list();
302
			if (array_key_exists($route_type, $controller_map))
303
			{
304
				$controller_class = $controller_map[$route_type];
305
			}
306
			else
307
			{
308
				$controller_class = $this->routes['convention']['default_controller'];
309
			}
310
311
			// Prepend the controller to the route parameters
312
			$route['controller'] = $controller_class;
313
314
			// Select the appropriate router method based on the http verb
315
			$add = (array_key_exists('verb', $route))
316
				? "add" . ucfirst(strtolower($route['verb'])) : "addGet";
317
318
			// Add the route to the router object
319
			if ( ! array_key_exists('tokens', $route))
320
			{
321
				$routes[] = $this->router->$add($name, $path)->addValues($route);
322
			}
323
			else
324
			{
325
				$tokens = $route['tokens'];
326
				unset($route['tokens']);
327
328
				$routes[] = $this->router->$add($name, $path)
329
					->addValues($route)
330
					->addTokens($tokens);
331
			}
332
		}
333
334
		return $routes;
335
	}
336
}
337
// End of Dispatcher.php