Спойлер: Роутер PHP: <?php namespace core; use Error; abstract class Router { private static $routes = []; private static $route = []; private static $params = []; private static $namespace = 'app\controllers\\'; public static function addRule($route, $params = []) { $route = preg_replace('~{([a-z]+)}~', '(?<$1>[a-z]++(?:-[a-z]++)?)', $route); $route = preg_replace('~{([a-z_]+):([^}]+)}~', '(?<$1>$2)', $route); self::$routes["~^{$route}$~"] = $params; } public static function dispatch($url) { if (!self::matchRoute($url)) { throw new Error('Страница не найдена'); } $controller = self::$namespace . self::$route['controller'] . 'Controller'; if (!class_exists($controller)) { throw new Error("Контроллер {$controller} не найден", 404); } $cObj = new $controller(self::$route); $action = self::$route['action'] . 'Action'; if (!method_exists($cObj, $action)) { throw new Error("Метод {$controller}::{$action} не найден", 404); } $cObj->$action(...self::$params); $cObj->getView(); } private static function matchRoute($url) { foreach (self::$routes as $route => $params) { if (preg_match($route, $url, $matches)) { foreach ($matches as $match => $param) { if (is_string($match)) { $params[$match] = $param; } } $params = array_filter($params); if (isset($params['module'])) { self::$namespace .= $params['module'] . '\\'; self::$route['module'] = $params['module'] . '/'; } self::$route['controller'] = self::upperCamelCase($params['controller'] ?? 'home'); self::$route['action'] = lcfirst(self::upperCamelCase($params['action'] ?? 'index')); unset($params['module'], $params['controller'], $params['action']); self::$params = $params; return true; } } return false; } private static function upperCamelCase($name) { return str_replace(' ', '', ucwords(str_replace('-', ' ', $name))); } } Теперь я задаю правила: PHP: // Правило рефссылки // Router::addRule('start/{referer_id:[1-9]\d*}', ['controller' => 'start', 'action' => 'referer']); Router::addRule('{controller:start}/{referer_id:[1-9]\d*}', ['action' => 'referer']); // Дефолт Router::addRule('{controller}?/?{action}?'); Router::dispatch(ltrim($_SERVER['REQUEST_URI'], '/')); Что происходит? Я подменил экшен, и у меня вместо StartController::indexAction отрабатывает StartController::refererAction Метод выглядит так: PHP: public function refererAction($referer_id) { debug($referer_id); // дебажим if ($referer_id and !isset($_SESSION['referer_id']) and User::findOne($referer_id)) { $_SESSION['referer_id'] = $referer_id; } $this->redirectToPage('/'); } Теперь если я набираю адрес /start/123, то всё отрабатывает как надо: в дебаге - string '123' (length=3) Но если я наберу /start/referer, то естественно отработает дефолт, потому что цикл матчера продолжает искать совпадения в массиве роутов и мы получаем - Too few arguments to function app\controllers\StartController::refererAction() Эту ошибку у меня отловит обработчик ошибок, покажет её красиво разрабу, а пользуну 404, но я не хочу ИМЕННО таким образом ловить её. Есть ли возможность сделать правило рефссылки ОБЯЗАТЕЛЬНЫМ и делать выброс в роутере при адресе /start/referer - это вообще можно как-то реализовать или дефолт не позволит этого сделать? P.S. я знаю что можно сделать public function refererAction($referer_id = 0), но всё же
Ну, сделай более общую маску идентификатора, чтобы /start/referer попал в экшин. --- Добавлено --- А внутри можешь делать проверку с более жесткой маской или надеяться на БД. Или, может, findOne сам выплюнет нечисловой id без обращения к БД.
@miketomlin вроде понял, нужно просто тупо написать правило под /start/referer, и там указать в массиве параметров referer_id => 0, что-то вроде такого то есть держать в массиве роутов дефолтное значение передаваемого параметра --- Добавлено --- @miketomlin либо избавиться от подмены и обращаться /start/referer/123, что наверное и сделаю...
Не. Говорю, сделай, чтобы и с буквами в id запрос попадал на обработку в экшин. --- Добавлено --- А внутри уже переводи в 404-ую запросы с левыми id.