150 lines
5.6 KiB
PHP
150 lines
5.6 KiB
PHP
<?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;
|
||
}
|
||
}
|