Files
rmphp-router/src/Router.php
2025-03-02 19:13:32 +03:00

150 lines
5.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Rmphp\Router;
use Psr\Http\Message\RequestInterface;
use Rmphp\Foundation\MatchObject;
use Rmphp\Foundation\RouterInterface;
class Router implements RouterInterface {
private array $rules = [];
private string $startPoint = "/";
/**
* @param string $mountPoint
*/
public function setStartPoint(string $mountPoint): void {
$this->startPoint = $mountPoint;
}
/**
* @param array $rules
*/
public function withRules(array $rules): void
{
$this->rules = [];
foreach ($rules as $rulesKey => $rulesNode) {
if (!isset($rulesNode['key'], $rulesNode['routes'])) continue;
// преобразуем псевдомаску в реальную маску
// заменяем алиасы на регвыражения
$realPattern = $rulesNode['key'];
$realPattern = preg_replace("'<([A-z0-9_]+?):@any>'", "(?P<$1>.*)", $realPattern);
$realPattern = preg_replace("'<([A-z0-9_]+?):@num>'", "(?P<$1>[0-9]+)", $realPattern);
$realPattern = preg_replace("'<([A-z0-9_]+?):@path>'", "(?P<$1>[^/]+)", $realPattern);
// поддерживаем свободное регулярное выражение в псевдомаске
$realPattern = preg_replace("'<([A-z0-9_]+?):(.+?)>'", "(?P<$1>$2)", $realPattern);
// заменяем алиасы на регвыражения
$realPattern = str_replace(["<@any>", "<@num>", "<@path>"], [".*", "[0-9]+", "[^/]+"], $realPattern);
// при наличии слеша в конце правила url должно строго ему соответствовать
$end = (str_ends_with($realPattern, "/")) ? "$" : "";
// меняем запись на паттерн
$this->rules[$rulesKey] = $rulesNode;
$this->rules[$rulesKey]['key'] = "'^".$realPattern.$end."'";
}
}
/**
* @param array $rules
* @return void
*/
public function withSet(array $rules): void
{
$this->rules = [];
foreach ($rules as $rulesKey => $rulesNode) {
if(!isset($rulesNode['key'], $rulesNode['routes'])) continue;
$rulesNode['key'] = trim($rulesNode['key'], '/');
if(!preg_match("'^[A-z0-9_/]+$'", $rulesNode['key'])) continue;
$this->rules[$rulesKey] = $rulesNode;
}
}
/**
* @param RequestInterface $request
* @return array|null
*/
public function match(RequestInterface $request): ?array {
foreach ($this->rules as $rule) {
// если для правила определен метод и он не совпал смотри далее
if(!empty($rule['withMethod']) && is_array($rule['withMethod']) && false === (array_search($request->getMethod(), $rule['withMethod']))) continue;
if(!empty($rule['withoutMethod']) && is_array($rule['withoutMethod']) && false !== (array_search($request->getMethod(), $rule['withoutMethod']))) continue;
// вычисляем рабочий url
$currentUrlString = preg_replace("'^".preg_quote($this->startPoint)."'", "/", $request->getUri()->getPath());
// в цикле проверяем совпадения текущей строки с правилами
if (preg_match($rule['key'], $currentUrlString, $matches)) {
$routes = [];
foreach ($rule['routes'] as $route) {
// если в результате есть именные ключи от ?P<name>, пытаемся произвести замену <name> в части inc
foreach ($matches as $matchesKey => $matchesVal) {
if (!is_numeric($matchesKey)) {
$route['action'] = str_replace("<".$matchesKey.">", ucfirst($matchesVal), $route['action']);
$route['method'] = str_replace("<".$matchesKey.">", ucfirst($matchesVal), $route['method']);
if (!empty($route['params'])) {
$route['params'] = str_replace("<".$matchesKey.">", $matchesVal, $route['params']);
}
}
}
// чистка маркеров
$route['action'] = preg_replace("'<.+>'", "", $route['action']);
$route['method'] = preg_replace("'<.+>'", "", $route['method']);
if (!empty($route['params'])) {
$route['params'] = preg_replace("'<.+>'", "", $route['params']);
}
$className = (!empty($route['action'])) ? $route['action'] : "";
$methodName = (!empty($route['method'])) ? $route['method'] : "";
$paramSet = (!empty($route['params'])) ? explode(",",str_replace(" ", "", $route['params'])) : [];
$params = [];
foreach ($paramSet as $key => $param){
if(empty($param)) continue;
$params[$key] = (preg_match("'^[0-9]+$'", $param)) ? (int) $param : $param;
}
$routes[] = new MatchObject($className, $methodName, $params);
}
return $routes;
}
}
return null;
}
/**
* @param RequestInterface $request
* @return array|null
*/
public function matchByArgv(RequestInterface $request): ?array {
foreach ($this->rules as $rule) {
// вычисляем строку для поиска
$currentString = trim(preg_replace("'^".preg_quote($this->startPoint)."/'", "", $request->getServerParams()['argv'][1]),'/');
// в цикле проверяем совпадения текущей строки с правилами
if ($rule['key'] == $currentString) {
$routes = [];
foreach ($rule['routes'] as $route) {
$className = (!empty($route['action'])) ? $route['action'] : "";
$methodName = (!empty($route['method'])) ? $route['method'] : "";
$params = [];
foreach ($request->getServerParams()['argv'] as $key => $param){
if($key < 2) continue;
$params[$key] = (preg_match("'^[0-9]+$'", $param)) ? (int) $param : $param;
}
$routes[] = new MatchObject($className, $methodName, $params);
}
return $routes;
}
}
return null;
}
}