Dispatcher::processRoute()   C
last analyzed

Complexity

Conditions 7
Paths 9

Size

Total Lines 43
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 23
nc 9
nop 1
dl 0
loc 43
rs 6.7272
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
/**
3
 * Hummingbird Anime Client
4
 *
5
 * An API client for Hummingbird to manage anime and manga watch lists
6
 *
7
 * PHP version 7
8
 *
9
 * @package     HummingbirdAnimeClient
10
 * @author      Timothy J. Warren <[email protected]>
11
 * @copyright   2015 - 2016  Timothy J. Warren
12
 * @license     http://www.opensource.org/licenses/mit-license.html  MIT License
13
 * @version     3.1
14
 * @link        https://github.com/timw4mail/HummingBirdAnimeClient
15
 */
16
17
namespace Aviat\AnimeClient;
18
19
use const Aviat\AnimeClient\{
20
	DEFAULT_CONTROLLER,
21
	DEFAULT_CONTROLLER_NAMESPACE,
22
	ERROR_MESSAGE_METHOD,
23
	NOT_FOUND_METHOD,
24
	SRC_DIR
25
};
26
27
use Aviat\Ion\Di\ContainerInterface;
28
use Aviat\Ion\Friend;
29
30
/**
31
 * Basic routing/ dispatch
32
 */
33
class Dispatcher extends RoutingBase {
34
35
	/**
36
	 * The route-matching object
37
	 * @var object $router
38
	 */
39
	protected $router;
40
41
	/**
42
	 * The route matcher
43
	 * @var object $matcher
44
	 */
45
	protected $matcher;
46
47
	/**
48
	 * Class wrapper for input superglobals
49
	 * @var Psr\Http\Message\ServerRequestInterface
50
	 */
51
	protected $request;
52
53
	/**
54
	 * Routes added to router
55
	 * @var array $output_routes
56
	 */
57
	protected $output_routes;
58
59
	/**
60
	 * Constructor
61
	 *
62
	 * @param ContainerInterface $container
63
	 */
64
	public function __construct(ContainerInterface $container)
65
	{
66
		parent::__construct($container);
67
		$this->router = $container->get('aura-router')->getMap();
68
		$this->matcher = $container->get('aura-router')->getMatcher();
69
		$this->request = $container->get('request');
70
71
		$this->output_routes = $this->_setupRoutes();
72
	}
73
74
	/**
75
	 * Get the current route object, if one matches
76
	 *
77
	 * @return object
78
	 */
79
	public function getRoute()
80
	{
81
		$logger = $this->container->getLogger('default');
82
83
		$raw_route = $this->request->getUri()->getPath();
84
		$route_path = "/" . trim($raw_route, '/');
85
86
		$logger->info('Dispatcher - Routing data from get_route method');
87
		$logger->info(print_r([
88
			'route_path' => $route_path
89
		], TRUE));
90
91
		return $this->matcher->match($this->request);
92
	}
93
94
	/**
95
	 * Get list of routes applied
96
	 *
97
	 * @return array
98
	 */
99
	public function getOutputRoutes()
100
	{
101
		return $this->output_routes;
102
	}
103
104
	/**
105
	 * Handle the current route
106
	 *
107
	 * @codeCoverageIgnore
108
	 * @param object|null $route
109
	 * @return void
110
	 */
111
	public function __invoke($route = NULL)
112
	{
113
		$logger = $this->container->getLogger('default');
114
115
		if (is_null($route))
116
		{
117
			$route = $this->getRoute();
118
119
			$logger->info('Dispatcher - Route invoke arguments');
120
			$logger->info(print_r($route, TRUE));
121
		}
122
123
		if ($route)
124
		{
125
			$parsed = $this->processRoute(new Friend($route));
0 ignored issues
show
Documentation introduced by
new \Aviat\Ion\Friend($route) is of type object<Aviat\Ion\Friend>, but the function expects a object<Aura\Router\Route>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
126
			$controllerName = $parsed['controller_name'];
127
			$actionMethod = $parsed['action_method'];
128
			$params = $parsed['params'];
129
		}
130
		else
131
		{
132
			// If not route was matched, return an appropriate http
133
			// error message
134
			$error_route = $this->getErrorParams();
135
			$controllerName = DEFAULT_CONTROLLER;
136
			$actionMethod = $error_route['action_method'];
137
			$params = $error_route['params'];
138
		}
139
140
		$this->call($controllerName, $actionMethod, $params);
141
	}
142
143
	/**
144
	 * Parse out the arguments for the appropriate controller for
145
	 * the current route
146
	 *
147
	 * @param \Aura\Router\Route $route
148
	 * @throws \LogicException
149
	 * @return array
150
	 */
151
	protected function processRoute($route)
152
	{
153
		if (array_key_exists('controller', $route->attributes))
154
		{
155
			$controller_name = $route->attributes['controller'];
156
		}
157
		else
158
		{
159
			throw new \LogicException("Missing controller");
160
		}
161
162
		// Get the full namespace for a controller if a short name is given
163
		if (strpos($controller_name, '\\') === FALSE)
164
		{
165
			$map = $this->getControllerList();
166
			$controller_name = $map[$controller_name];
167
		}
168
169
		$action_method = (array_key_exists('action', $route->attributes))
170
			? $route->attributes['action']
171
			: NOT_FOUND_METHOD;
172
173
		$params = [];
174
		if ( ! empty($route->__get('tokens')))
175
		{
176
			$tokens = array_keys($route->__get('tokens'));
177
			foreach ($tokens as $param)
178
			{
179
				if (array_key_exists($param, $route->attributes))
180
				{
181
					$params[$param] = $route->attributes[$param];
182
				}
183
			}
184
		}
185
		$logger = $this->container->getLogger('default');
186
		$logger->info(json_encode($params));
187
188
		return [
189
			'controller_name' => $controller_name,
190
			'action_method' => $action_method,
191
			'params' => $params
192
		];
193
	}
194
195
	/**
196
	 * Get the type of route, to select the current controller
197
	 *
198
	 * @return string
199
	 */
200
	public function getController()
201
	{
202
		$route_type = $this->__get('default_list');
203
		$request_uri = $this->request->getUri()->getPath();
204
		$path = trim($request_uri, '/');
205
206
		$segments = explode('/', $path);
207
		$controller = reset($segments);
208
209
		if (empty($controller))
210
		{
211
			$controller = $route_type;
212
		}
213
214
		return $controller;
215
	}
216
217
	/**
218
	 * Get the list of controllers in the default namespace
219
	 *
220
	 * @return array
221
	 */
222
	public function getControllerList()
223
	{
224
		$default_namespace = DEFAULT_CONTROLLER_NAMESPACE;
225
		$path = str_replace('\\', '/', $default_namespace);
226
		$path = str_replace('Aviat/AnimeClient/', '', $path);
227
		$path = trim($path, '/');
228
		$actual_path = realpath(_dir(SRC_DIR, $path));
229
		$class_files = glob("{$actual_path}/*.php");
230
231
		$controllers = [];
232
233
		foreach ($class_files as $file)
234
		{
235
			$raw_class_name = basename(str_replace(".php", "", $file));
236
			$path = strtolower(basename($raw_class_name));
237
			$class_name = trim($default_namespace . '\\' . $raw_class_name, '\\');
238
239
			$controllers[$path] = $class_name;
240
		}
241
242
		return $controllers;
243
	}
244
245
	/**
246
	 * Create the controller object and call the appropriate
247
	 * method
248
	 *
249
	 * @param  string $controllerName - The full namespace of the controller class
250
	 * @param  string $method
251
	 * @param  array  $params
252
	 * @return void
253
	 */
254
	protected function call($controllerName, $method, array $params)
255
	{
256
		$logger = $this->container->getLogger('default');
257
258
		$controller = new $controllerName($this->container);
259
260
		// Run the appropriate controller method
261
		$logger->debug('Dispatcher - controller arguments');
262
		$logger->debug(print_r($params, TRUE));
263
		call_user_func_array([$controller, $method], $params);
264
	}
265
266
	/**
267
	 * Get the appropriate params for the error page
268
	 * pased on the failed route
269
	 *
270
	 * @return array|false
271
	 */
272
	protected function getErrorParams()
273
	{
274
		$logger = $this->container->getLogger('default');
275
		$failure = $this->matcher->getFailedRoute();
276
277
		$logger->info('Dispatcher - failed route');
278
		$logger->info(print_r($failure, TRUE));
279
280
		$action_method = ERROR_MESSAGE_METHOD;
281
282
		$params = [];
283
284
		switch($failure->failedRule) {
285
			case 'Aura\Router\Rule\Allows':
286
				$params = [
287
					'http_code' => 405,
288
					'title' => '405 Method Not Allowed',
289
					'message' => 'Invalid HTTP Verb'
290
				];
291
			break;
292
293
			case 'Aura\Router\Rule\Accepts':
294
				$params = [
295
					'http_code' => 406,
296
					'title' => '406 Not Acceptable',
297
					'message' => 'Unacceptable content type'
298
				];
299
			break;
300
301
			default:
302
				// Fall back to a 404 message
303
				$action_method = NOT_FOUND_METHOD;
304
			break;
305
		}
306
307
		return [
308
			'params' => $params,
309
			'action_method' => $action_method
310
		];
311
	}
312
313
	/**
314
	 * Select controller based on the current url, and apply its relevent routes
315
	 *
316
	 * @return array
317
	 */
318
	protected function _setupRoutes()
319
	{
320
		$route_type = $this->getController();
321
322
		// Add routes
323
		$routes = [];
324
		foreach ($this->routes as $name => &$route)
325
		{
326
			$path = $route['path'];
327
			unset($route['path']);
328
329
			$controller_map = $this->getControllerList();
330
			$controller_class = (array_key_exists($route_type, $controller_map))
331
				? $controller_map[$route_type]
332
				: DEFAULT_CONTROLLER;
333
334
			if (array_key_exists($route_type, $controller_map))
335
			{
336
				$controller_class = $controller_map[$route_type];
337
			}
338
339
			// Prepend the controller to the route parameters
340
			$route['controller'] = $controller_class;
341
342
			// Select the appropriate router method based on the http verb
343
			$add = (array_key_exists('verb', $route))
344
				? strtolower($route['verb'])
345
				: "get";
346
347
			// Add the route to the router object
348
			if ( ! array_key_exists('tokens', $route))
349
			{
350
				$routes[] = $this->router->$add($name, $path)->defaults($route);
351
			}
352
			else
353
			{
354
				$tokens = $route['tokens'];
355
				unset($route['tokens']);
356
357
				$routes[] = $this->router->$add($name, $path)
358
					->defaults($route)
359
					->tokens($tokens);
360
			}
361
		}
362
363
		return $routes;
364
	}
365
}
366
// End of Dispatcher.php