Completed
Push — master ( b44893...3c1244 )
by Timothy
04:48
created

Dispatcher::get_output_routes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
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 - 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
		$controller_name = AnimeClient::DEFAULT_CONTROLLER;
98
		$action_method = AnimeClient::NOT_FOUND_METHOD;
99
		$params = [];
100
101
		if (is_null($route))
102
		{
103
			$route = $this->get_route();
104
			$error_handler->addDataTable('route_args', (array)$route);
105
		}
106
107
		if ( ! $route)
108
		{
109
			$failure = $this->router->getFailedRoute();
110
			$error_handler->addDataTable('failed_route', (array)$failure);
111
			$action_method = AnimeClient::ERROR_MESSAGE_METHOD;
112
113
			switch(TRUE)
114
			{
115
				case $failure->failedMethod():
116
					$params['title'] = '405 Method Not Allowed';
117
					$params['message'] = 'Invalid HTTP Verb';
118
				break;
119
120
				case $failure->failedAccept():
121
					$params['title'] = '406 Not Acceptable';
122
					$params['message'] = 'Unacceptable content type';
123
				break;
124
125
				default:
126
					$action_method = AnimeClient::NOT_FOUND_METHOD;
127
				break;
128
			}
129
		}
130
		else
131
		{
132
			if (isset($route->params['controller']))
133
			{
134
				$controller_name = $route->params['controller'];
135
			}
136
137
			if (isset($route->params['action']))
138
			{
139
				$action_method = $route->params['action'];
140
			}
141
142
			if (is_null($controller_name))
143
			{
144
				throw new \LogicException("Missing controller");
145
			}
146
147
			if (strpos($controller_name, '\\') === FALSE)
148
			{
149
				$map = $this->get_controller_list();
150
				$controller_name = $map[$controller_name];
151
			}
152
153
			$params = (isset($route->params['params'])) ? $route->params['params'] : [];
154
155
			if ( ! empty($route->tokens))
156
			{
157
				foreach ($route->tokens as $key => $v)
158
				{
159
					if (array_key_exists($key, $route->params))
160
					{
161
						$params[$key] = $route->params[$key];
162
					}
163
				}
164
			}
165
		}
166
167
		$controller = new $controller_name($this->container);
168
169
		// Run the appropriate controller method
170
		$error_handler->addDataTable('controller_args', $params);
171
		call_user_func_array([$controller, $action_method], $params);
172
	}
173
174
	/**
175
	 * Get the type of route, to select the current controller
176
	 *
177
	 * @return string
178
	 */
179
	public function get_controller()
180
	{
181
		$route_type = $this->__get('default_list');
182
		$request_uri = $this->request->url->get(PHP_URL_PATH);
183
		$path = trim($request_uri, '/');
184
185
		$segments = explode('/', $path);
186
		$controller = reset($segments);
187
188
		if (empty($controller))
189
		{
190
			$controller = $route_type;
191
		}
192
193
		return $controller;
194
	}
195
196
	/**
197
	 * Get the list of controllers in the default namespace
198
	 *
199
	 * @return array
200
	 */
201
	public function get_controller_list()
202
	{
203
		$default_namespace = AnimeClient::DEFAULT_CONTROLLER_NAMESPACE;
204
		$path = str_replace('\\', '/', $default_namespace);
205
		$path = trim($path, '/');
206
		$actual_path = \_dir(SRC_DIR, $path);
207
208
		$class_files = glob("{$actual_path}/*.php");
209
210
		$controllers = [];
211
212
		foreach ($class_files as $file)
213
		{
214
			$raw_class_name = basename(str_replace(".php", "", $file));
215
			$path = strtolower(basename($raw_class_name));
216
			$class_name = trim($default_namespace . '\\' . $raw_class_name, '\\');
217
218
			$controllers[$path] = $class_name;
219
		}
220
221
		return $controllers;
222
	}
223
224
	/**
225
	 * Select controller based on the current url, and apply its relevent routes
226
	 *
227
	 * @return array
228
	 */
229
	protected function _setup_routes()
230
	{
231
		$route_type = $this->get_controller();
232
233
		// Add routes
234
		$routes = [];
235
		foreach ($this->routes as $name => &$route)
236
		{
237
			$path = $route['path'];
238
			unset($route['path']);
239
240
			$controller_map = $this->get_controller_list();
241
			if (array_key_exists($route_type, $controller_map))
242
			{
243
				$controller_class = $controller_map[$route_type];
244
			}
245
			else
246
			{
247
				$controller_class = AnimeClient::DEFAULT_CONTROLLER;
248
			}
249
250
			// Prepend the controller to the route parameters
251
			$route['controller'] = $controller_class;
252
253
			// Select the appropriate router method based on the http verb
254
			$add = (array_key_exists('verb', $route))
255
				? "add" . ucfirst(strtolower($route['verb']))
256
				: "addGet";
257
258
			// Add the route to the router object
259
			if ( ! array_key_exists('tokens', $route))
260
			{
261
				$routes[] = $this->router->$add($name, $path)->addValues($route);
262
			}
263
			else
264
			{
265
				$tokens = $route['tokens'];
266
				unset($route['tokens']);
267
268
				$routes[] = $this->router->$add($name, $path)
269
					->addValues($route)
270
					->addTokens($tokens);
271
			}
272
		}
273
274
		return $routes;
275
	}
276
}
277
// End of Dispatcher.php