forked from mirzaev/minimal
174 lines
6.4 KiB
PHP
174 lines
6.4 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace mirzaev\minimal;
|
||
|
||
// Файлы проекта
|
||
use mirzaev\minimal\core;
|
||
|
||
/**
|
||
* Маршрутизатор
|
||
*
|
||
* @package mirzaev\minimal
|
||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||
*/
|
||
final class router
|
||
{
|
||
/**
|
||
* @var array $router Реестр маршрутов
|
||
*/
|
||
protected array $routes = [];
|
||
|
||
/**
|
||
* Записать маршрут
|
||
*
|
||
* @param string $route Маршрут
|
||
* @param string $handler Обработчик - инстанции контроллера и модели (не обязательно), без постфиксов
|
||
* @param ?string $method Вызываемый метод в инстанции контроллера обработчика
|
||
* @param ?string $request HTTP-метод запроса (GET, POST, PUT...)
|
||
* @param ?string $model Инстанция модели (переопределение инстанции модели в $target)
|
||
*
|
||
* @return void
|
||
*/
|
||
public function write(
|
||
string $route,
|
||
string $handler,
|
||
?string $method = 'index',
|
||
?string $request = 'GET',
|
||
?string $model = null
|
||
): void {
|
||
// Запись в реестр
|
||
$this->routes[$route][$request] = [
|
||
'controller' => $handler,
|
||
'model' => $model ?? $handler,
|
||
'method' => $method
|
||
];
|
||
}
|
||
|
||
/**
|
||
* Обработать маршрут
|
||
*
|
||
* @param ?string $uri URI запроса (https://domain.com/foo/bar)
|
||
* @param ?string $method Метод запроса (GET, POST, PUT...)
|
||
* @param ?core $core Инстанция системного ядра
|
||
*/
|
||
public function handle(?string $uri = null, ?string $method = null, ?core $core = new core): ?string
|
||
{
|
||
// Инициализация значений по умолчанию
|
||
$uri ??= $_SERVER['REQUEST_URI'] ?? '/';
|
||
$method ??= $_SERVER["REQUEST_METHOD"] ?? 'GET';
|
||
|
||
// Инициализация URL запроса (/foo/bar)
|
||
$url = parse_url($uri, PHP_URL_PATH);
|
||
|
||
// Универсализация маршрута
|
||
$url = self::universalize($url);
|
||
|
||
// Сортировка реестра маршрутов от большего ключа к меньшему (кешируется)
|
||
krsort($this->routes);
|
||
|
||
// Поиск директорий в ссылке
|
||
preg_match_all('/[^\/]+/', $url, $directories);
|
||
|
||
// Инициализация директорий
|
||
$directories = $directories[0];
|
||
|
||
foreach ($this->routes as $route => $data) {
|
||
// Перебор маршрутов
|
||
|
||
// Универсализация маршрута
|
||
$route = self::universalize($route);
|
||
|
||
// Поиск директорий
|
||
preg_match_all('/[^\/]+/', $route, $data['directories']);
|
||
|
||
// Инициализация директорий
|
||
$data['directories'] = $data['directories'][0];
|
||
|
||
if (count($directories) === count($data['directories'])) {
|
||
// Входит в диапазон маршрут (совпадает количество директорий у ссылки и маршрута)
|
||
|
||
// Инициализация реестра переменных
|
||
$data['vars'] = [];
|
||
|
||
foreach ($data['directories'] as $index => &$directory) {
|
||
// Перебор директорий
|
||
|
||
if (preg_match('/\$([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]+)/', $directory) === 1) {
|
||
// Директория является переменной (.../$variable/...)
|
||
|
||
// Запись в реестр переменных
|
||
$directory = $data['vars'][trim($directory, '$')] = $directories[$index];
|
||
}
|
||
}
|
||
|
||
// Реиницилазция маршрута
|
||
$route = self::universalize(implode('/', $data['directories']));
|
||
|
||
// Проверка на пустой маршрут
|
||
if (empty($route)) $route = '/';
|
||
|
||
if (mb_stripos($route, $url, 0, "UTF-8") === 0 && mb_strlen($route, 'UTF-8') <= mb_strlen($url, 'UTF-8')) {
|
||
// Идентифицирован маршрут (длина не меньше длины запрошенного URL)
|
||
|
||
if (array_key_exists($method, $data)) {
|
||
// Идентифицирован метод маршрута (GET, POST, PUT...)
|
||
|
||
$route = $data[$method];
|
||
|
||
if (class_exists($controller = $core->namespace . '\\controllers\\' . $route['controller'] . $core->controller::POSTFIX)) {
|
||
// Найден контроллер
|
||
|
||
// Инициализация инстанции ядра контроллера
|
||
$controller = new $controller;
|
||
|
||
// Инициализация инстанции ядра модели
|
||
if (class_exists($model = $core->namespace . '\\models\\' . $route['model'] . $core->model::POSTFIX)) $controller->model = new $model;
|
||
|
||
// Вызов связанного с маршрутом методв и возврат (успех)
|
||
return $controller->{$route['method']}($data['vars'] + $_REQUEST, $_FILES);
|
||
}
|
||
}
|
||
|
||
// Выход из цикла (провал)
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Возврат (провал)
|
||
return $this->error($core);
|
||
}
|
||
|
||
/**
|
||
* Сгенерировать ответ с ошибкой
|
||
*
|
||
* Вызывает метод error404 в инстанции контроллера ошибок
|
||
*
|
||
* @param ?core $core Инстанция системного ядра
|
||
*
|
||
* @return ?string HTML-документ
|
||
*/
|
||
private function error(core $core = new core): ?string
|
||
{
|
||
return class_exists($class = '\\' . $core->namespace . '\\controllers\\errors' . $core->controller::POSTFIX)
|
||
&& method_exists($class, $method = 'error404')
|
||
? (new $class)->$method()
|
||
: null;
|
||
}
|
||
|
||
/**
|
||
* Универсализировать маршрут
|
||
*
|
||
* @param string $route Маршрут
|
||
*
|
||
* @return string Универсализированный маршрут
|
||
*/
|
||
private function universalize(string $route): string
|
||
{
|
||
// Если не записан "/" в начале, то записать, затем, если записан "/" в конце, то удалить
|
||
return preg_replace('/(.+)\/$/', '$1', preg_replace('/^([^\/])/', '/$1', $route));
|
||
}
|
||
}
|