| Total Complexity | 57 |
| Total Lines | 474 |
| Duplicated Lines | 0 % |
| Changes | 0 | ||
Complex classes like AutoRouter 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 AutoRouter, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 27 | final class AutoRouter implements AutoRouterInterface |
||
| 28 | { |
||
| 29 | /** |
||
| 30 | * Sous-répertoire contenant la classe contrôleur demandée. |
||
| 31 | * Principalement utilisé par 'autoRoute'. |
||
| 32 | */ |
||
| 33 | private ?string $directory = null; |
||
| 34 | |||
| 35 | /** |
||
| 36 | * Le nom de la classe du contrôleur. |
||
| 37 | */ |
||
| 38 | private string $controller; |
||
| 39 | |||
| 40 | /** |
||
| 41 | * Nom de la méthode à utiliser. |
||
| 42 | */ |
||
| 43 | private string $method; |
||
| 44 | |||
| 45 | /** |
||
| 46 | * Tableau de paramètres de la méthode du contrôleur. |
||
| 47 | * |
||
| 48 | * @var string[] |
||
| 49 | */ |
||
| 50 | private array $params = []; |
||
| 51 | |||
| 52 | /** |
||
| 53 | * Namespace des controleurs |
||
| 54 | */ |
||
| 55 | private string $namespace; |
||
| 56 | |||
| 57 | /** |
||
| 58 | * Segments de l'URI |
||
| 59 | * |
||
| 60 | * @var string[] |
||
| 61 | */ |
||
| 62 | private array $segments = []; |
||
| 63 | |||
| 64 | /** |
||
| 65 | * Position du contrôleur dans les segments URI. |
||
| 66 | * Null pour le contrôleur par défaut. |
||
| 67 | */ |
||
| 68 | private ?int $controllerPos = null; |
||
| 69 | |||
| 70 | /** |
||
| 71 | * Position de la méthode dans les segments URI. |
||
| 72 | * Null pour la méthode par défaut. |
||
| 73 | */ |
||
| 74 | private ?int $methodPos = null; |
||
| 75 | |||
| 76 | /** |
||
| 77 | * Position du premier Paramètre dans les segments URI. |
||
| 78 | * Null pour les paramètres non definis. |
||
| 79 | */ |
||
| 80 | private ?int $paramPos = null; |
||
| 81 | |||
| 82 | /** |
||
| 83 | * Constructeur |
||
| 84 | * |
||
| 85 | * @param class-string[] $protectedControllers Liste des contrôleurs enregistrés pour le verbe CLI qui ne doivent pas être accessibles sur le Web. |
||
|
|
|||
| 86 | * @param string $defaultNamespace Espace de noms par défaut pour les contrôleurs. |
||
| 87 | * @param string $defaultController Nom du controleur par defaut. |
||
| 88 | * @param string $defaultMethod Nom de la methode par defaut. |
||
| 89 | * @param bool $translateURIDashes Indique si les tirets dans les URI doivent être convertis en traits de soulignement lors de la détermination des noms de méthode. |
||
| 90 | */ |
||
| 91 | public function __construct( |
||
| 92 | private array $protectedControllers, |
||
| 93 | string $namespace, |
||
| 94 | private string $defaultController, |
||
| 95 | private string $defaultMethod, |
||
| 96 | private bool $translateURIDashes |
||
| 97 | ) { |
||
| 98 | $this->namespace = rtrim($namespace, '\\'); |
||
| 99 | |||
| 100 | // Definir les valeurs par defaut |
||
| 101 | $this->controller = $this->defaultController; |
||
| 102 | } |
||
| 103 | |||
| 104 | private function createSegments(string $uri): array |
||
| 105 | { |
||
| 106 | $segments = explode('/', $uri); |
||
| 107 | $segments = array_filter($segments, static fn ($segment) => $segment !== ''); |
||
| 108 | |||
| 109 | // réindexer numériquement le tableau, en supprimant les lacunes |
||
| 110 | return array_values($segments); |
||
| 111 | } |
||
| 112 | |||
| 113 | /** |
||
| 114 | * Recherchez le premier contrôleur correspondant au segment URI. |
||
| 115 | * |
||
| 116 | * S'il y a un contrôleur correspondant au premier segment, la recherche s'arrête là. |
||
| 117 | * Les segments restants sont des paramètres du contrôleur. |
||
| 118 | * |
||
| 119 | * @return bool true si une classe de contrôleur est trouvée. |
||
| 120 | */ |
||
| 121 | private function searchFirstController(): bool |
||
| 122 | { |
||
| 123 | $segments = $this->segments; |
||
| 124 | |||
| 125 | $controller = '\\' . $this->namespace; |
||
| 126 | |||
| 127 | $controllerPos = -1; |
||
| 128 | |||
| 129 | while ($segments !== []) { |
||
| 130 | $segment = array_shift($segments); |
||
| 131 | $controllerPos++; |
||
| 132 | |||
| 133 | $class = $this->translateURIDashes(ucfirst($segment)); |
||
| 134 | |||
| 135 | // dès que nous rencontrons un segment qui n'est pas compatible PSR-4, arrêter la recherche |
||
| 136 | if (! $this->isValidSegment($class)) { |
||
| 137 | return false; |
||
| 138 | } |
||
| 139 | |||
| 140 | $controller .= '\\' . $class; |
||
| 141 | |||
| 142 | if (class_exists($controller)) { |
||
| 143 | $this->controller = $controller; |
||
| 144 | $this->controllerPos = $controllerPos; |
||
| 145 | |||
| 146 | // Le premier élément peut être un nom de méthode. |
||
| 147 | $this->params = $segments; |
||
| 148 | if ($segments !== []) { |
||
| 149 | $this->paramPos = $this->controllerPos + 1; |
||
| 150 | } |
||
| 151 | |||
| 152 | return true; |
||
| 153 | } |
||
| 154 | } |
||
| 155 | |||
| 156 | return false; |
||
| 157 | } |
||
| 158 | |||
| 159 | /** |
||
| 160 | * Recherchez le dernier contrôleur par défaut correspondant aux segments URI. |
||
| 161 | * |
||
| 162 | * @return bool true si une classe de contrôleur est trouvée. |
||
| 163 | */ |
||
| 164 | private function searchLastDefaultController(): bool |
||
| 217 | } |
||
| 218 | |||
| 219 | /** |
||
| 220 | * Recherche contrôleur, méthode et params dans l'URI. |
||
| 221 | * |
||
| 222 | * @return array [directory_name, controller_name, controller_method, params] |
||
| 223 | */ |
||
| 224 | public function getRoute(string $uri, string $httpVerb): array |
||
| 225 | { |
||
| 226 | $httpVerb = strtolower($httpVerb); |
||
| 227 | |||
| 228 | // Reinitialise les parametres de la methode du controleur. |
||
| 229 | $this->params = []; |
||
| 230 | |||
| 231 | $defaultMethod = $httpVerb . ucfirst($this->defaultMethod); |
||
| 232 | $this->method = $defaultMethod; |
||
| 233 | |||
| 234 | $this->segments = $this->createSegments($uri); |
||
| 235 | |||
| 236 | //Verifier les routes de modules |
||
| 237 | if ( |
||
| 238 | $this->segments !== [] |
||
| 239 | && ($routingConfig = (object) config('routing')) |
||
| 240 | && array_key_exists($this->segments[0], $routingConfig->module_routes) |
||
| 241 | ) { |
||
| 242 | $uriSegment = array_shift($this->segments); |
||
| 243 | $this->namespace = rtrim($routingConfig->module_routes[$uriSegment], '\\'); |
||
| 244 | } |
||
| 245 | |||
| 246 | if ($this->searchFirstController()) { |
||
| 247 | // Le contrôleur a ete trouvé. |
||
| 248 | $baseControllerName = Helpers::classBasename($this->controller); |
||
| 249 | |||
| 250 | // Empêcher l'accès au chemin de contrôleur par défaut |
||
| 251 | if (strtolower($baseControllerName) === strtolower($this->defaultController)) { |
||
| 252 | throw new PageNotFoundException( |
||
| 253 | 'Impossible d\'accéder au contrôleur par défaut "' . $this->controller . '" avec le nom du contrôleur comme chemin de l\'URI.' |
||
| 254 | ); |
||
| 255 | } |
||
| 256 | } elseif ($this->searchLastDefaultController()) { |
||
| 257 | // Le controleur par defaut a ete trouve. |
||
| 258 | $baseControllerName = Helpers::classBasename($this->controller); |
||
| 259 | } else { |
||
| 260 | // Aucun controleur trouvé |
||
| 261 | throw new PageNotFoundException('Aucun contrôleur trouvé pour: ' . $uri); |
||
| 262 | } |
||
| 263 | |||
| 264 | // Le premier élément peut être un nom de méthode. |
||
| 265 | /** @var string[] $params */ |
||
| 266 | $params = $this->params; |
||
| 267 | |||
| 268 | $methodParam = array_shift($params); |
||
| 269 | |||
| 270 | $method = ''; |
||
| 271 | if ($methodParam !== null) { |
||
| 272 | $method = $httpVerb . ucfirst($this->translateURIDashes($methodParam)); |
||
| 273 | } |
||
| 274 | |||
| 275 | if ($methodParam !== null && method_exists($this->controller, $method)) { |
||
| 276 | // Methode trouvee. |
||
| 277 | $this->method = $method; |
||
| 278 | $this->params = $params; |
||
| 279 | |||
| 280 | // Mise a jour des positions. |
||
| 281 | $this->methodPos = $this->paramPos; |
||
| 282 | if ($params === []) { |
||
| 283 | $this->paramPos = null; |
||
| 284 | } |
||
| 285 | if ($this->paramPos !== null) { |
||
| 286 | $this->paramPos++; |
||
| 287 | } |
||
| 288 | |||
| 289 | // Empêcher l'accès à la méthode du contrôleur par défaut |
||
| 290 | if (strtolower($baseControllerName) === strtolower($this->defaultController)) { |
||
| 291 | throw new PageNotFoundException( |
||
| 292 | 'Impossible d\'accéder au contrôleur par défaut "' . $this->controller . '::' . $this->method . '"' |
||
| 293 | ); |
||
| 294 | } |
||
| 295 | |||
| 296 | // Empêcher l'accès au chemin de méthode par défaut |
||
| 297 | if (strtolower($this->method) === strtolower($defaultMethod)) { |
||
| 298 | throw new PageNotFoundException( |
||
| 299 | 'Impossible d\'accéder à la méthode par défaut "' . $this->method . '" avec le nom de méthode comme chemin d\'URI.' |
||
| 300 | ); |
||
| 301 | } |
||
| 302 | } elseif (method_exists($this->controller, $defaultMethod)) { |
||
| 303 | // La methode par defaut a ete trouvée |
||
| 304 | $this->method = $defaultMethod; |
||
| 305 | } else { |
||
| 306 | // Aucune methode trouvee |
||
| 307 | throw PageNotFoundException::controllerNotFound($this->controller, $method); |
||
| 308 | } |
||
| 309 | |||
| 310 | // Vérifiez le contrôleur n'est pas défini dans les routes. |
||
| 311 | $this->protectDefinedRoutes(); |
||
| 312 | |||
| 313 | // Assurez-vous que le contrôleur n'a pas la méthode _remap(). |
||
| 314 | $this->checkRemap(); |
||
| 315 | |||
| 316 | // Assurez-vous que les segments URI pour le contrôleur et la méthode |
||
| 317 | // ne contiennent pas de soulignement lorsque $translateURIDashes est true. |
||
| 318 | $this->checkUnderscore($uri); |
||
| 319 | |||
| 320 | // Verifier le nombre de parametres |
||
| 321 | try { |
||
| 322 | $this->checkParameters($uri); |
||
| 323 | } catch (MethodNotFoundException $e) { |
||
| 324 | throw PageNotFoundException::controllerNotFound($this->controller, $this->method); |
||
| 325 | } |
||
| 326 | |||
| 327 | $this->setDirectory(); |
||
| 328 | |||
| 329 | return [$this->directory, $this->controllerName(), $this->methodName(), $this->params]; |
||
| 330 | } |
||
| 331 | |||
| 332 | private function checkParameters(string $uri): void |
||
| 333 | { |
||
| 334 | try { |
||
| 335 | $refClass = new ReflectionClass($this->controller); |
||
| 336 | } catch (ReflectionException $e) { |
||
| 337 | throw PageNotFoundException::controllerNotFound($this->controller, $this->method); |
||
| 338 | } |
||
| 339 | |||
| 340 | try { |
||
| 341 | $refMethod = $refClass->getMethod($this->method); |
||
| 342 | $refParams = $refMethod->getParameters(); |
||
| 343 | } catch (ReflectionException $e) { |
||
| 344 | throw new MethodNotFoundException(); |
||
| 345 | } |
||
| 346 | |||
| 347 | if (! $refMethod->isPublic()) { |
||
| 348 | throw new MethodNotFoundException(); |
||
| 349 | } |
||
| 350 | |||
| 351 | if (count($refParams) < count($this->params)) { |
||
| 352 | throw new PageNotFoundException( |
||
| 353 | 'Le nombre de param dans l\'URI est supérieur aux paramètres de la méthode du contrôleur.' |
||
| 354 | . ' Handler:' . $this->controller . '::' . $this->method |
||
| 355 | . ', URI:' . $uri |
||
| 356 | ); |
||
| 357 | } |
||
| 358 | } |
||
| 359 | |||
| 360 | private function checkRemap(): void |
||
| 361 | { |
||
| 362 | try { |
||
| 363 | $refClass = new ReflectionClass($this->controller); |
||
| 364 | $refClass->getMethod('_remap'); |
||
| 365 | |||
| 366 | throw new PageNotFoundException( |
||
| 367 | 'AutoRouterImproved ne prend pas en charge la methode `_remap()`.' |
||
| 368 | . ' Contrôleur:' . $this->controller |
||
| 369 | ); |
||
| 370 | } catch (ReflectionException $e) { |
||
| 371 | // Ne rien faire |
||
| 372 | } |
||
| 373 | } |
||
| 374 | |||
| 375 | private function checkUnderscore(string $uri): void |
||
| 376 | { |
||
| 377 | if ($this->translateURIDashes === false) { |
||
| 378 | return; |
||
| 379 | } |
||
| 380 | |||
| 381 | $paramPos = $this->paramPos ?? count($this->segments); |
||
| 382 | |||
| 383 | for ($i = 0; $i < $paramPos; $i++) { |
||
| 384 | if (strpos($this->segments[$i], '_') !== false) { |
||
| 385 | throw new PageNotFoundException( |
||
| 386 | 'AutoRouterImproved interdit l\'accès à l\'URI' |
||
| 387 | . ' contenant les undescore ("' . $this->segments[$i] . '")' |
||
| 388 | . ' quand $translate_uri_dashes est activé.' |
||
| 389 | . ' Veuillez utiliser les tiret.' |
||
| 390 | . ' Handler:' . $this->controller . '::' . $this->method |
||
| 391 | . ', URI:' . $uri |
||
| 392 | ); |
||
| 393 | } |
||
| 394 | } |
||
| 395 | } |
||
| 396 | |||
| 397 | /** |
||
| 398 | * Renvoie true si la chaîne $segment fournie représente un segment d'espace de noms/répertoire valide conforme à PSR-4 |
||
| 399 | * |
||
| 400 | * La regex vient de https://www.php.net/manual/en/language.variables.basics.php |
||
| 401 | */ |
||
| 402 | private function isValidSegment(string $segment): bool |
||
| 403 | { |
||
| 404 | return (bool) preg_match('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/', $segment); |
||
| 405 | } |
||
| 406 | |||
| 407 | private function translateURIDashes(string $segment): string |
||
| 408 | { |
||
| 409 | return $this->translateURIDashes |
||
| 410 | ? str_replace('-', '_', $segment) |
||
| 411 | : $segment; |
||
| 412 | } |
||
| 413 | |||
| 414 | /** |
||
| 415 | * Obtenez le chemin du dossier du contrôleur et définissez-le sur la propriété. |
||
| 416 | */ |
||
| 417 | private function setDirectory(): void |
||
| 418 | { |
||
| 419 | $segments = explode('\\', trim($this->controller, '\\')); |
||
| 420 | |||
| 421 | // Supprimer le court nom de classe. |
||
| 422 | array_pop($segments); |
||
| 423 | |||
| 424 | $namespaces = implode('\\', $segments); |
||
| 425 | |||
| 426 | $dir = str_replace( |
||
| 427 | '\\', |
||
| 428 | '/', |
||
| 429 | ltrim(substr($namespaces, strlen($this->namespace)), '\\') |
||
| 430 | ); |
||
| 431 | |||
| 432 | if ($dir !== '') { |
||
| 433 | $this->directory = $dir . '/'; |
||
| 434 | } |
||
| 435 | } |
||
| 436 | |||
| 437 | private function protectDefinedRoutes(): void |
||
| 438 | { |
||
| 439 | $controller = strtolower($this->controller); |
||
| 440 | |||
| 441 | foreach ($this->protectedControllers as $controllerInRoutes) { |
||
| 442 | $routeLowerCase = strtolower($controllerInRoutes); |
||
| 443 | |||
| 444 | if ($routeLowerCase === $controller) { |
||
| 445 | throw new PageNotFoundException( |
||
| 446 | 'Impossible d\'accéder à un contrôleur définie dans les routes. Contrôleur : ' . $controllerInRoutes |
||
| 447 | ); |
||
| 448 | } |
||
| 449 | } |
||
| 450 | } |
||
| 451 | |||
| 452 | /** |
||
| 453 | * Renvoie le nom du sous-répertoire dans lequel se trouve le contrôleur. |
||
| 454 | * Relatif à CONTROLLER_PATH |
||
| 455 | * |
||
| 456 | * @deprecated 1.0 |
||
| 457 | */ |
||
| 458 | public function directory(): string |
||
| 459 | { |
||
| 460 | return ! empty($this->directory) ? $this->directory : ''; |
||
| 461 | } |
||
| 462 | |||
| 463 | /** |
||
| 464 | * Renvoie le nom du contrôleur matché |
||
| 465 | * |
||
| 466 | * @return closure|string |
||
| 467 | */ |
||
| 468 | private function controllerName() |
||
| 469 | { |
||
| 470 | if (! is_string($this->controller)) { |
||
| 471 | return $this->controller; |
||
| 472 | } |
||
| 473 | |||
| 474 | return $this->translateURIDashes |
||
| 475 | ? str_replace('-', '_', trim($this->controller, '/\\')) |
||
| 476 | : Text::toPascalCase($this->controller); |
||
| 477 | } |
||
| 478 | |||
| 479 | /** |
||
| 480 | * Retourne le nom de la méthode à exécuter |
||
| 481 | */ |
||
| 482 | private function methodName(): string |
||
| 487 | } |
||
| 488 | |||
| 489 | /** |
||
| 490 | * Construit un nom de contrôleur valide |
||
| 491 | * |
||
| 492 | * @deprecated 1.0 |
||
| 493 | */ |
||
| 494 | public function makeController(string $name): string |
||
| 501 | } |
||
| 502 | } |
||
| 503 |