132 lines
4.5 KiB
PHP
132 lines
4.5 KiB
PHP
<?php
|
||
/**
|
||
* @package Bicycle
|
||
* @author Egor Isaev
|
||
* @description Route.php
|
||
* @copyright (c) 03/06/2026
|
||
*/
|
||
|
||
namespace System\Classes;
|
||
|
||
/**
|
||
* Автоматический роутинг без конфигурации: разбирает URI и находит
|
||
* контроллер на файловой системе в App/Controller (регистронезависимо).
|
||
*/
|
||
class Route
|
||
{
|
||
/**
|
||
* Разбирает URI и возвращает массив с ключами controller, action, params.
|
||
*
|
||
* Поддерживаемые форматы URL:
|
||
* / → Index::index
|
||
* /controller → Controller::index
|
||
* /controller/action → Controller::action
|
||
* /controller/action/p1/p2 → Controller::action + params
|
||
* /subdir/controller/action → Subdir/Controller::action (если subdir — папка в App/Controller)
|
||
*
|
||
* @param string $uri URI без ведущего/замыкающего слэша
|
||
* @return array{controller:string, action:string, params:array}
|
||
*/
|
||
public static function resolve(string $uri): array
|
||
{
|
||
if ($uri === '') {
|
||
return self::make('Index', 'index', []);
|
||
}
|
||
|
||
$parts = explode('/', $uri);
|
||
|
||
$controller = self::findController($parts[0], '');
|
||
|
||
if ($controller !== null || !isset($parts[1])) {
|
||
return self::make(
|
||
$controller ?? ucfirst($parts[0]),
|
||
$parts[1] ?? 'index',
|
||
array_slice($parts, 2)
|
||
);
|
||
}
|
||
|
||
$subdir_controller = self::findController($parts[1], $parts[0]);
|
||
|
||
if ($subdir_controller !== null) {
|
||
return self::make(
|
||
$subdir_controller,
|
||
$parts[2] ?? 'index',
|
||
array_slice($parts, 3)
|
||
);
|
||
}
|
||
|
||
// Ни плоский контроллер, ни subdir не найдены — возвращаем flat-роутинг
|
||
return self::make(
|
||
ucfirst($parts[0]),
|
||
$parts[1] ?? 'index',
|
||
array_slice($parts, 2)
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Ищет файл {name}Controller.php в App/Classes[/subdir] без учёта регистра.
|
||
* Возвращает имя контроллера (или subdir/имя) без суффикса Controller, либо null.
|
||
*
|
||
* @param string $name Базовое имя контроллера из URI
|
||
* @param string $subdir Имя подкаталога или '' для плоского поиска
|
||
* @return string|null
|
||
*/
|
||
private static function findController(string $name, string $subdir): ?string
|
||
{
|
||
$base = DOCROOT . '/App/Controller/';
|
||
$dir = $base;
|
||
|
||
if ($subdir !== '') {
|
||
foreach (is_dir($base) ? scandir($base) : [] as $entry) {
|
||
if ($entry !== '.' && $entry !== '..'
|
||
&& is_dir($base . $entry)
|
||
&& strtolower($entry) === strtolower($subdir)
|
||
) {
|
||
$dir = $base . $entry . '/';
|
||
$subdir = $entry;
|
||
break;
|
||
}
|
||
}
|
||
if ($dir === $base) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
if (!is_dir($dir)) {
|
||
return null;
|
||
}
|
||
|
||
$needle = strtolower($name . 'Controller');
|
||
|
||
foreach (scandir($dir) as $file) {
|
||
if (pathinfo($file, PATHINFO_EXTENSION) !== 'php') {
|
||
continue;
|
||
}
|
||
$basename = pathinfo($file, PATHINFO_FILENAME);
|
||
if (strtolower($basename) === $needle) {
|
||
$clean = substr($basename, 0, -10); // убираем 'Controller'
|
||
return $subdir !== '' ? $subdir . '/' . $clean : $clean;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* Собирает результат роутинга в единый формат.
|
||
*
|
||
* @param string $controller Имя контроллера (возможно с subdir через /)
|
||
* @param string $action Имя действия без суффикса Action
|
||
* @param array $params Позиционные параметры из URI
|
||
* @return array{controller:string, action:string, params:array}
|
||
*/
|
||
private static function make(string $controller, string $action, array $params): array
|
||
{
|
||
return [
|
||
'controller' => $controller,
|
||
'action' => $action,
|
||
'params' => $params,
|
||
];
|
||
}
|
||
}
|