Router   D
last analyzed

Complexity

Total Complexity 58

Size/Duplication

Total Lines 259
Duplicated Lines 0 %

Test Coverage

Coverage 93.97%

Importance

Changes 2
Bugs 1 Features 1
Metric Value
wmc 58
eloc 105
c 2
b 1
f 1
dl 0
loc 259
rs 4.5599
ccs 109
cts 116
cp 0.9397

19 Methods

Rating   Name   Duplication   Size   Complexity  
A cleanParam() 0 5 2
A checkRouteName() 0 9 6
A getRoute() 0 13 6
A startAll() 0 2 1
A setParamsInOrder() 0 22 6
A getRouteInfoByName() 0 7 3
A getRoutes() 0 2 1
B getRouteUrlParts() 0 33 10
A setExpired() 0 2 1
A start() 0 2 1
A path() 0 2 1
A _getURL() 0 8 2
A getStatusCode() 0 2 1
A setStatusCode() 0 2 1
B getRouteByName() 0 14 7
A slashPath() 0 8 3
A startRest() 0 2 1
A url() 0 2 1
A getRoute_() 0 16 4

How to fix   Complexity   

Complex Class

Complex classes like Router often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Router, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Ubiquity\controllers;
4
5
use Ubiquity\cache\CacheManager;
6
use Ubiquity\controllers\router\RouterStatus;
7
use Ubiquity\controllers\traits\RouterAdminTrait;
8
use Ubiquity\controllers\traits\RouterModifierTrait;
9
use Ubiquity\controllers\traits\RouterTestTrait;
10
use Ubiquity\log\Logger;
11
use Ubiquity\utils\http\URequest;
12
13
/**
14
 * Router manager.
15
 * Ubiquity\controllers$Router
16
 * This class is part of Ubiquity
17
 *
18
 * @author jcheron <[email protected]>
19
 * @version 1.1.1
20
 *
21
 */
22
class Router {
23
	use RouterModifierTrait, RouterAdminTrait, RouterTestTrait;
24
25
	protected static $routes;
26
	protected static $statusCode;
27 16
28 16
	private static function cleanParam(string $param): string {
29
		if (\substr($param, -1) === '/') {
30
			return \substr($param, 0, -1);
31 16
		}
32
		return $param;
33
	}
34 36
35 36
	private static function getRoute_(&$routeDetails, $routePath, $matches, $cachedResponse) {
36 36
		self::$statusCode = RouterStatus::OK;
37 14
		if (!isset ($routeDetails ['controller'])) {
38 14
			$method = \strtolower($_SERVER ['REQUEST_METHOD']);
39 14
			if (isset ($routeDetails [$method])) {
40 14
				$routeDetailsMethod = $routeDetails [$method];
41
				return self::getRouteUrlParts(['path' => $routePath, 'details' => $routeDetailsMethod], $matches, $routeDetailsMethod ['cache'] ?? false, $routeDetailsMethod ['duration'] ?? null, $cachedResponse);
42 3
			}
43
			self::$statusCode = RouterStatus::METHOD_NOT_ALLOWED;
44 23
		} else {
45
			return self::getRouteUrlParts(['path' => $routePath, 'details' => $routeDetails], $matches, $routeDetails ['cache'] ?? false, $routeDetails ['duration'] ?? null, $cachedResponse);
46 3
		}
47
		if (self::$statusCode === RouterStatus::OK) {
48
			self::$statusCode = RouterStatus::NOT_FOUND;
49 3
		}
50
		return false;
51
	}
52 4
53 4
	protected static function _getURL($routePath, $params) {
54 4
		$result = \preg_replace_callback('~\((.*?)\)~', function () use (&$params) {
55 4
			return \array_shift($params);
56 4
		}, $routePath);
57
		if (\count($params) > 0) {
58
			$result = \rtrim($result, '/') . '/' . \implode('/', $params);
59 4
		}
60
		return $result;
61
	}
62 7
63 7
	protected static function checkRouteName($routeDetails, $name) {
64 7
		if (!isset ($routeDetails ['name'])) {
65 7
			foreach ($routeDetails as $methodRouteDetail) {
66
				if (isset ($methodRouteDetail ['name']) && $methodRouteDetail ['name'] == $name) {
67
					return true;
68
				}
69
			}
70 7
		}
71
		return isset ($routeDetails ['name']) && $routeDetails ['name'] == $name;
72
	}
73 18
74 18
	protected static function setParamsInOrder($paramsOrder, $params) {
75 18
		$index = 0;
76 18
		$newParams = [];
77 18
		foreach ($paramsOrder as $order) {
78 1
			if ($order === '*') {
79 1
				if (isset ($params [$index])) {
80
					$newParams = \array_merge($newParams, \array_diff(\explode('/', $params [$index]), ['']));
81 1
				}
82
				break;
83 18
			}
84 5
			if (($order [0] ?? '') === '~') {
85 5
				$order = \intval(\substr($order, 1, 1));
86 5
				if (isset ($params [$order])) {
87 5
					$newParams = \array_merge($newParams, \array_diff(\explode('/', $params [$order]), ['']));
88
					break;
89
				}
90 16
			}
91 16
			$newParams [] = self::cleanParam($params [$order]);
92 16
			unset ($params [$order]);
93
			$index++;
94 18
		}
95
		return $newParams;
96
	}
97
98
	/**
99
	 * Starts the router by loading normal routes (not rest).
100 44
	 */
101 44
	public static function start(): void {
102
		self::$routes = CacheManager::getControllerCache();
103
	}
104
105
	/**
106
	 * Starts the router by loading rest routes (not normal routes).
107 5
	 */
108 5
	public static function startRest(): void {
109
		self::$routes = CacheManager::getControllerCache(true);
110
	}
111
112
	/**
113
	 * Starts the router by loading all routes (normal + rest routes).
114 58
	 */
115 58
	public static function startAll(): void {
116
		self::$routes = \array_merge(CacheManager::getControllerCache(), CacheManager::getControllerCache(true));
117
	}
118
119
	/**
120
	 * Returns the route corresponding to a path.
121
	 *
122
	 * @param string $path The route path
123
	 * @param boolean $cachedResponse
124
	 * @return boolean|mixed[]|string
125 71
	 */
126 71
	public static function getRoute($path, $cachedResponse = true, $debug = false) {
127 71
		$path = self::slashPath($path);
128 5
		if (isset (self::$routes [$path]) && !$debug) { // No direct access to route in debug mode (for maintenance mode activation)
129
			return self::getRoute_(self::$routes [$path], $path, [$path], $cachedResponse);
130 70
		}
131 70
		foreach (self::$routes as $routePath => $routeDetails) {
132 33
			if (\preg_match("@^{$routePath}\$@s", $path, $matches)) {
133 33
				if (($r = self::getRoute_($routeDetails, $routePath, $matches, $cachedResponse)) !== false) {
134
					return $r;
135
				}
136
			}
137 52
		}
138
		return false;
139
	}
140
141
	/**
142
	 * Returns the generated path from a route.
143
	 *
144
	 * @param string $name name of the route
145
	 * @param array $parameters array of the route parameters. default : []
146
	 * @param boolean $absolute
147 5
	 */
148 5
	public static function getRouteByName($name, $parameters = [], $absolute = true) {
149 5
		foreach (self::$routes as $routePath => $routeDetails) {
150 5
			if (self::checkRouteName($routeDetails, $name)) {
151
				if (\trim($routePath, '/') == '_default') {
152
					return ($absolute) ? '/' : '';
153 5
				}
154 4
				if (\count($parameters) > 0) {
155
					$routePath = self::_getURL($routePath, $parameters);
156 5
				}
157 5
				$routePath = \str_replace('//', '/', \preg_replace('~\((.*?)\)~', '', $routePath));
158
				return ($absolute) ? $routePath : \ltrim($routePath, '/');
159
			}
160 3
		}
161
		return false;
162
	}
163 2
164 2
	public static function getRouteInfoByName($name) {
165 2
		foreach (self::$routes as $routeDetails) {
166 2
			if (self::checkRouteName($routeDetails, $name)) {
167
				return $routeDetails;
168
			}
169 1
		}
170
		return false;
171
	}
172
173
	/**
174
	 * Returns the generated path from a route.
175
	 *
176
	 * @param string $name The route name
177
	 * @param array $parameters default: []
178
	 * @param boolean $absolute true if the path is absolute (/ at first)
179
	 * @return boolean|string|array|mixed the generated path (/path/to/route)
180 3
	 */
181 3
	public static function path($name, $parameters = [], $absolute = false) {
182
		return self::getRouteByName($name, $parameters, $absolute);
183
	}
184
185
	/**
186
	 * Returns the generated url from a route.
187
	 *
188
	 * @param string $name the route name
189
	 * @param array $parameters default: []
190
	 * @return string the generated url (http://myApp/path/to/route)
191 1
	 */
192 1
	public static function url($name, $parameters = []): string {
193
		return URequest::getUrl(self::getRouteByName($name, $parameters, false));
194
	}
195 36
196 36
	public static function getRouteUrlParts($routeArray, $params, $cached = false, $duration = null, $cachedResponse = true) {
197 36
		$realPath = \current($params);
198 36
		\array_shift($params);
199 36
		$routeDetails = $routeArray ['details'];
200 1
		if ($routeDetails ['controller'] instanceof \Closure) {
201 1
			$result = ['callback' => $routeDetails ['controller']];
202
			$resultStr = 'callable function';
203 35
		} else {
204 35
			$mainParams = null;
205 2
			if (($mainMethodParams = $routeDetails['main.params'] ?? null) !== null) {
206 2
				foreach ($mainMethodParams as $index => $mainMethodParam) {
207
					$mainParams[$mainMethodParam] = $params[$index];
208 2
				}
209
				$params = \array_slice($params, $index + 1);
210 35
			}
211 35
			$result = ['controller' => \str_replace("\\\\", "\\", $routeDetails ['controller']), 'action' => $routeDetails ['action'], 'mainParams' => $mainParams];
212
			$resultStr = \json_encode($result);
213 36
		}
214 18
		if (($paramsOrder = $routeDetails ['parameters']) && (\count($paramsOrder) > 0)) {
215
			$result['params'] = self::setParamsInOrder($paramsOrder, $params);
216 36
		}
217 36
		if (!$cached || !$cachedResponse) {
218 36
			Logger::info('Router', \sprintf('Route found for %s : %s', $routeArray ['path'], $resultStr), 'getRouteUrlParts');
219
			if (isset ($routeDetails ['callback'])) {
220 1
				// Used for maintenance mode
221 1
				if ($routeDetails ['callback'] instanceof \Closure) {
222
					return $routeDetails ['callback'] ($result);
223
				}
224 35
			}
225
			return $result;
226
		}
227
		Logger::info('Router', sprintf('Route found for %s (from cache) : %s', $realPath, $resultStr), 'getRouteUrlParts');
228
		return CacheManager::getRouteCache($realPath, $result, $duration);
229
	}
230
231
	/**
232
	 * Adds a slash before and after a path
233
	 *
234
	 * @param string $path The path to modify
235
	 * @return string The path with slashes
236 74
	 */
237 74
	public static function slashPath($path): string {
238 72
		if (\substr($path, 0, 1) !== '/') {
239
			$path = '/' . $path;
240 74
		}
241 70
		if (\substr($path, -1) !== '/') {
242
			$path = $path . '/';
243 74
		}
244
		return $path;
245
	}
246
247
	/**
248
	 * Declares a route as expired.
249
	 *
250
	 * @param string $routePath
251 1
	 */
252 1
	public static function setExpired($routePath): void {
253
		CacheManager::setExpired($routePath);
254
	}
255
256
	/**
257
	 * Returns the array of loaded routes.
258
	 *
259
	 * @return array|mixed
260 71
	 */
261 71
	public static function getRoutes() {
262
		return self::$routes;
263
	}
264
265
	/**
266
	 * Return router response status code.
267
	 * @return int
268
	 * @since 2.4.5
269 1
	 */
270 1
	public static function getStatusCode(): int {
271
		return self::$statusCode;
272
	}
273
274
	/**
275
	 * Set router response status code.
276
	 * @param mixed $statusCode
277
	 * @since 2.5.2
278
	 */
279
	public static function setStatusCode($statusCode): void {
280
		self::$statusCode = $statusCode;
281
	}
282
}
283