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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
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 |
||
| 26 | class Router |
||
| 27 | { |
||
| 28 | use Options; |
||
| 29 | |||
| 30 | /** |
||
| 31 | * Or should be as properties? |
||
| 32 | */ |
||
| 33 | const DEFAULT_MODULE = 'index'; |
||
| 34 | const DEFAULT_CONTROLLER = 'index'; |
||
| 35 | const ERROR_MODULE = 'error'; |
||
| 36 | const ERROR_CONTROLLER = 'index'; |
||
| 37 | |||
| 38 | /** |
||
| 39 | * @var string base URL |
||
| 40 | */ |
||
| 41 | protected $baseUrl; |
||
| 42 | |||
| 43 | /** |
||
| 44 | * @var string REQUEST_URI minus Base URL |
||
| 45 | */ |
||
| 46 | protected $cleanUri; |
||
| 47 | |||
| 48 | /** |
||
| 49 | * @var string default module |
||
| 50 | */ |
||
| 51 | protected $defaultModule = self::DEFAULT_MODULE; |
||
| 52 | |||
| 53 | /** |
||
| 54 | * @var string default Controller |
||
| 55 | */ |
||
| 56 | protected $defaultController = self::DEFAULT_CONTROLLER; |
||
| 57 | |||
| 58 | /** |
||
| 59 | * @var string error module |
||
| 60 | */ |
||
| 61 | protected $errorModule = self::ERROR_MODULE; |
||
| 62 | |||
| 63 | /** |
||
| 64 | * @var string error Controller |
||
| 65 | */ |
||
| 66 | protected $errorController = self::ERROR_CONTROLLER; |
||
| 67 | |||
| 68 | /** |
||
| 69 | * @var array instance parameters |
||
| 70 | */ |
||
| 71 | protected $params = []; |
||
| 72 | |||
| 73 | /** |
||
| 74 | * @var array instance raw parameters |
||
| 75 | */ |
||
| 76 | protected $rawParams = []; |
||
| 77 | |||
| 78 | /** |
||
| 79 | * @var array routers map |
||
| 80 | */ |
||
| 81 | protected $routers = []; |
||
| 82 | |||
| 83 | /** |
||
| 84 | * @var array reverse map |
||
| 85 | */ |
||
| 86 | protected $reverse = []; |
||
| 87 | |||
| 88 | /** |
||
| 89 | * Constructor of Router |
||
| 90 | */ |
||
| 91 | 674 | public function __construct() |
|
| 92 | { |
||
| 93 | 674 | $routers = Cache::get('router.routers'); |
|
| 94 | 674 | $reverse = Cache::get('router.reverse'); |
|
| 95 | |||
| 96 | 674 | if (!$routers || !$reverse) { |
|
| 97 | 674 | list($routers, $reverse) = $this->prepareRouterData(); |
|
| 98 | 674 | Cache::set('router.routers', $routers, Cache::TTL_NO_EXPIRY, ['system']); |
|
| 99 | 674 | Cache::set('router.reverse', $reverse, Cache::TTL_NO_EXPIRY, ['system']); |
|
| 100 | } |
||
| 101 | |||
| 102 | 674 | $this->routers = $routers; |
|
| 103 | 674 | $this->reverse = $reverse; |
|
| 104 | 674 | } |
|
| 105 | |||
| 106 | /** |
||
| 107 | * Initial routers data from controllers |
||
| 108 | * |
||
| 109 | * @return array[] |
||
| 110 | */ |
||
| 111 | 674 | private function prepareRouterData() |
|
| 112 | { |
||
| 113 | 674 | $routers = []; |
|
| 114 | 674 | $reverse = []; |
|
| 115 | 674 | $path = Application::getInstance()->getPath() . '/modules/*/controllers/*.php'; |
|
| 116 | 674 | foreach (new \GlobIterator($path) as $file) { |
|
| 117 | /* @var \SplFileInfo $file */ |
||
| 118 | 674 | $module = $file->getPathInfo()->getPathInfo()->getBasename(); |
|
| 119 | 674 | $controller = $file->getBasename('.php'); |
|
| 120 | 674 | $controllerInstance = new Controller($module, $controller); |
|
| 121 | 674 | $meta = $controllerInstance->getMeta(); |
|
| 122 | 674 | if ($routes = $meta->getRoute()) { |
|
| 123 | 674 | foreach ($routes as $route => $pattern) { |
|
| 124 | 674 | if (!isset($reverse[$module])) { |
|
| 125 | 674 | $reverse[$module] = []; |
|
| 126 | } |
||
| 127 | |||
| 128 | 674 | $reverse[$module][$controller] = ['route' => $route, 'params' => $meta->getParams()]; |
|
| 129 | |||
| 130 | $rule = [ |
||
| 131 | $route => [ |
||
| 132 | 674 | 'pattern' => $pattern, |
|
| 133 | 674 | 'module' => $module, |
|
| 134 | 674 | 'controller' => $controller, |
|
| 135 | 674 | 'params' => $meta->getParams() |
|
| 136 | ] |
||
| 137 | ]; |
||
| 138 | |||
| 139 | // static routers should be first, than routes with variables `$...` |
||
| 140 | // all routes begin with slash `/` |
||
| 141 | 674 | if (strpos($route, '$')) { |
|
| 142 | 674 | $routers[] = $rule; |
|
| 143 | } else { |
||
| 144 | 674 | array_unshift($routers, $rule); |
|
| 145 | } |
||
| 146 | } |
||
| 147 | } |
||
| 148 | } |
||
| 149 | 674 | $routers = array_merge(...$routers); |
|
| 150 | 674 | return [$routers, $reverse]; |
|
| 151 | } |
||
| 152 | |||
| 153 | /** |
||
| 154 | * Get the base URL. |
||
| 155 | * |
||
| 156 | * @return string |
||
| 157 | */ |
||
| 158 | 42 | public function getBaseUrl() |
|
| 159 | { |
||
| 160 | 42 | return $this->baseUrl; |
|
| 161 | } |
||
| 162 | |||
| 163 | /** |
||
| 164 | * Set the base URL. |
||
| 165 | * |
||
| 166 | * @param string $baseUrl |
||
| 167 | * @return void |
||
| 168 | */ |
||
| 169 | 674 | public function setBaseUrl($baseUrl) |
|
| 170 | { |
||
| 171 | 674 | $this->baseUrl = rtrim($baseUrl, '/') . '/'; |
|
| 172 | 674 | } |
|
| 173 | |||
| 174 | /** |
||
| 175 | * Get an action parameter |
||
| 176 | * |
||
| 177 | * @param string $key |
||
| 178 | * @param mixed $default Default value to use if key not found |
||
| 179 | * @return mixed |
||
| 180 | */ |
||
| 181 | public function getParam($key, $default = null) |
||
| 182 | { |
||
| 183 | return $this->params[$key] ?? $default; |
||
| 184 | } |
||
| 185 | |||
| 186 | /** |
||
| 187 | * Set an action parameter |
||
| 188 | * |
||
| 189 | * A $value of null will unset the $key if it exists |
||
| 190 | * |
||
| 191 | * @param string $key |
||
| 192 | * @param mixed $value |
||
| 193 | * @return void |
||
| 194 | */ |
||
| 195 | 5 | public function setParam($key, $value) |
|
| 196 | { |
||
| 197 | 5 | $key = (string)$key; |
|
| 198 | |||
| 199 | 5 | if ((null === $value) && isset($this->params[$key])) { |
|
| 200 | unset($this->params[$key]); |
||
| 201 | 5 | } elseif (null !== $value) { |
|
| 202 | 5 | $this->params[$key] = $value; |
|
| 203 | } |
||
| 204 | 5 | } |
|
| 205 | |||
| 206 | /** |
||
| 207 | * Get parameters |
||
| 208 | * |
||
| 209 | * @return array |
||
| 210 | */ |
||
| 211 | public function getParams() |
||
| 212 | { |
||
| 213 | return $this->params; |
||
| 214 | } |
||
| 215 | |||
| 216 | /** |
||
| 217 | * Get raw params, w/out module and controller |
||
| 218 | * |
||
| 219 | * @return array |
||
| 220 | */ |
||
| 221 | public function getRawParams() |
||
| 222 | { |
||
| 223 | return $this->rawParams; |
||
| 224 | } |
||
| 225 | |||
| 226 | /** |
||
| 227 | * Get default module |
||
| 228 | * |
||
| 229 | * @return string |
||
| 230 | */ |
||
| 231 | 44 | public function getDefaultModule() |
|
| 232 | { |
||
| 233 | 44 | return $this->defaultModule; |
|
| 234 | } |
||
| 235 | |||
| 236 | /** |
||
| 237 | * Set default module |
||
| 238 | * |
||
| 239 | * @param string $defaultModule |
||
| 240 | * @return void |
||
| 241 | */ |
||
| 242 | public function setDefaultModule($defaultModule) |
||
| 243 | { |
||
| 244 | $this->defaultModule = $defaultModule; |
||
| 245 | } |
||
| 246 | |||
| 247 | /** |
||
| 248 | * Get default controller |
||
| 249 | * |
||
| 250 | * @return string |
||
| 251 | */ |
||
| 252 | 44 | public function getDefaultController() |
|
| 253 | { |
||
| 254 | 44 | return $this->defaultController; |
|
| 255 | } |
||
| 256 | |||
| 257 | /** |
||
| 258 | * Set default controller |
||
| 259 | * |
||
| 260 | * @param string $defaultController |
||
| 261 | * @return void |
||
| 262 | */ |
||
| 263 | public function setDefaultController($defaultController) |
||
| 264 | { |
||
| 265 | $this->defaultController = $defaultController; |
||
| 266 | } |
||
| 267 | |||
| 268 | /** |
||
| 269 | * Get error module |
||
| 270 | * |
||
| 271 | * @return string |
||
| 272 | */ |
||
| 273 | 3 | public function getErrorModule() |
|
| 274 | { |
||
| 275 | 3 | return $this->errorModule; |
|
| 276 | } |
||
| 277 | |||
| 278 | /** |
||
| 279 | * Set error module |
||
| 280 | * |
||
| 281 | * @param string $errorModule |
||
| 282 | * @return void |
||
| 283 | */ |
||
| 284 | public function setErrorModule($errorModule) |
||
| 285 | { |
||
| 286 | $this->errorModule = $errorModule; |
||
| 287 | } |
||
| 288 | |||
| 289 | /** |
||
| 290 | * Get error controller |
||
| 291 | * |
||
| 292 | * @return string |
||
| 293 | */ |
||
| 294 | 3 | public function getErrorController() |
|
| 295 | { |
||
| 296 | 3 | return $this->errorController; |
|
| 297 | } |
||
| 298 | |||
| 299 | /** |
||
| 300 | * Set error controller |
||
| 301 | * |
||
| 302 | * @param string $errorController |
||
| 303 | * @return void |
||
| 304 | */ |
||
| 305 | public function setErrorController($errorController) |
||
| 306 | { |
||
| 307 | $this->errorController = $errorController; |
||
| 308 | } |
||
| 309 | |||
| 310 | /** |
||
| 311 | * Build URL to controller |
||
| 312 | * |
||
| 313 | * @param string $module |
||
| 314 | * @param string $controller |
||
| 315 | * @param array $params |
||
| 316 | * @return string |
||
| 317 | */ |
||
| 318 | 34 | public function getUrl($module = self::DEFAULT_MODULE, $controller = self::DEFAULT_CONTROLLER, array $params = []) |
|
| 319 | { |
||
| 320 | 34 | $module = $module ?? Request::getModule(); |
|
| 321 | 34 | $controller = $controller ?? Request::getController(); |
|
| 322 | |||
| 323 | 34 | if (isset($this->reverse[$module], $this->reverse[$module][$controller])) { |
|
| 324 | 5 | return $this->urlCustom($module, $controller, $params); |
|
| 325 | } |
||
| 326 | |||
| 327 | 29 | return $this->urlRoute($module, $controller, $params); |
|
| 328 | } |
||
| 329 | |||
| 330 | /** |
||
| 331 | * Build full URL to controller |
||
| 332 | * |
||
| 333 | * @param string $module |
||
| 334 | * @param string $controller |
||
| 335 | * @param array $params |
||
| 336 | * @return string |
||
| 337 | */ |
||
| 338 | 1 | public function getFullUrl( |
|
| 339 | $module = self::DEFAULT_MODULE, |
||
| 340 | $controller = self::DEFAULT_CONTROLLER, |
||
| 341 | array $params = [] |
||
| 342 | ) { |
||
| 343 | 1 | $scheme = Request::getInstance()->getUri()->getScheme() . '://'; |
|
| 344 | 1 | $host = Request::getInstance()->getUri()->getHost(); |
|
| 345 | 1 | $url = $this->getUrl($module, $controller, $params); |
|
| 346 | 1 | return $scheme . $host . $url; |
|
| 347 | } |
||
| 348 | |||
| 349 | /** |
||
| 350 | * Build URL by custom route |
||
| 351 | * |
||
| 352 | * @param string $module |
||
| 353 | * @param string $controller |
||
| 354 | * @param array $params |
||
| 355 | * @return string |
||
| 356 | */ |
||
| 357 | 5 | protected function urlCustom($module, $controller, $params) |
|
| 358 | { |
||
| 359 | 5 | $url = $this->reverse[$module][$controller]['route']; |
|
| 360 | |||
| 361 | 5 | $getParams = []; |
|
| 362 | 5 | foreach ($params as $key => $value) { |
|
| 363 | // sub-array as GET params |
||
| 364 | 4 | if (is_array($value)) { |
|
| 365 | 1 | $getParams[$key] = $value; |
|
| 366 | 1 | continue; |
|
| 367 | } |
||
| 368 | 3 | $url = str_replace('{$' . $key . '}', $value, $url, $replaced); |
|
| 369 | // if not replaced, setup param as GET |
||
| 370 | 3 | if (!$replaced) { |
|
| 371 | $getParams[$key] = $value; |
||
| 372 | } |
||
| 373 | } |
||
| 374 | // clean optional params |
||
| 375 | 5 | $url = preg_replace('/\{\$[a-z0-9-_]+\}/i', '', $url); |
|
| 376 | // clean regular expression (.*) |
||
| 377 | 5 | $url = preg_replace('/\(\.\*\)/', '', $url); |
|
| 378 | // replace "//" with "/" |
||
| 379 | 5 | $url = str_replace('//', '/', $url); |
|
| 380 | |||
| 381 | 5 | if (!empty($getParams)) { |
|
| 382 | 1 | $url .= '?' . http_build_query($getParams); |
|
| 383 | } |
||
| 384 | 5 | return $this->getBaseUrl() . ltrim($url, '/'); |
|
| 385 | } |
||
| 386 | |||
| 387 | /** |
||
| 388 | * Build URL by default route |
||
| 389 | * |
||
| 390 | * @param string $module |
||
| 391 | * @param string $controller |
||
| 392 | * @param array $params |
||
| 393 | * @return string |
||
| 394 | */ |
||
| 395 | 29 | protected function urlRoute($module, $controller, $params) |
|
| 396 | { |
||
| 397 | 29 | $url = $this->getBaseUrl(); |
|
| 398 | |||
| 399 | 29 | if (empty($params)) { |
|
| 400 | 15 | if ($controller === self::DEFAULT_CONTROLLER) { |
|
| 401 | 10 | if ($module === self::DEFAULT_MODULE) { |
|
| 402 | 7 | return $url; |
|
| 403 | } |
||
| 404 | 3 | return $url . $module; |
|
| 405 | } |
||
| 406 | } |
||
| 407 | |||
| 408 | 20 | $url .= $module . '/' . $controller; |
|
| 409 | 20 | $getParams = []; |
|
| 410 | 20 | foreach ($params as $key => $value) { |
|
| 411 | // sub-array as GET params |
||
| 412 | 15 | if (is_array($value)) { |
|
| 413 | 1 | $getParams[$key] = $value; |
|
| 414 | 1 | continue; |
|
| 415 | } |
||
| 416 | 14 | $url .= '/' . urlencode((string)$key) . '/' . urlencode((string)$value); |
|
| 417 | } |
||
| 418 | 20 | if (!empty($getParams)) { |
|
| 419 | 1 | $url .= '?' . http_build_query($getParams); |
|
| 420 | } |
||
| 421 | 20 | return $url; |
|
| 422 | } |
||
| 423 | |||
| 424 | /** |
||
| 425 | * Process routing |
||
| 426 | * |
||
| 427 | * @return \Bluz\Router\Router |
||
| 428 | */ |
||
| 429 | 6 | public function process() |
|
| 446 | |||
| 447 | /** |
||
| 448 | * Process default router |
||
| 449 | * |
||
| 450 | * @return bool |
||
| 451 | */ |
||
| 452 | 6 | protected function processDefault() |
|
| 457 | |||
| 458 | /** |
||
| 459 | * Process custom router |
||
| 460 | * |
||
| 461 | * @return bool |
||
| 462 | */ |
||
| 463 | 5 | protected function processCustom() |
|
| 464 | { |
||
| 465 | 5 | $uri = '/' . $this->getCleanUri(); |
|
| 466 | 5 | foreach ($this->routers as $router) { |
|
| 467 | 5 | if (preg_match($router['pattern'], $uri, $matches)) { |
|
| 468 | $this->setParam('_module', $router['module']); |
||
| 469 | $this->setParam('_controller', $router['controller']); |
||
| 470 | |||
| 471 | foreach ($router['params'] as $param => $type) { |
||
| 472 | if (isset($matches[$param])) { |
||
| 473 | $this->setParam($param, $matches[$param]); |
||
| 474 | } |
||
| 475 | } |
||
| 476 | return true; |
||
| 477 | } |
||
| 478 | } |
||
| 479 | 5 | return false; |
|
| 480 | } |
||
| 481 | |||
| 482 | /** |
||
| 483 | * Process router by default rules |
||
| 484 | * |
||
| 485 | * Default routers examples |
||
| 486 | * / |
||
| 487 | * /:module/ |
||
| 488 | * /:module/:controller/ |
||
| 489 | * /:module/:controller/:key1/:value1/:key2/:value2... |
||
| 490 | * |
||
| 491 | * @return bool |
||
| 492 | */ |
||
| 493 | 5 | protected function processRoute() |
|
| 528 | |||
| 529 | /** |
||
| 530 | * Reset Request |
||
| 531 | * |
||
| 532 | * @return void |
||
| 533 | */ |
||
| 534 | 6 | protected function resetRequest() |
|
| 554 | |||
| 555 | /** |
||
| 556 | * Get the request URI without baseUrl |
||
| 557 | * |
||
| 558 | * @return string |
||
| 559 | */ |
||
| 560 | 6 | public function getCleanUri() |
|
| 561 | { |
||
| 562 | 6 | if ($this->cleanUri === null) { |
|
| 563 | 6 | $uri = Request::getUri()->getPath(); |
|
| 564 | 6 | if ($this->getBaseUrl() && strpos($uri, $this->getBaseUrl()) === 0) { |
|
| 565 | 6 | $uri = substr($uri, strlen($this->getBaseUrl())); |
|
| 566 | } |
||
| 567 | 6 | $this->cleanUri = $uri; |
|
| 568 | } |
||
| 569 | 6 | return $this->cleanUri; |
|
| 570 | } |
||
| 571 | } |
||
| 572 |
As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next
break.There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.
To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.