Роутер в PHP: от простого switch до гибкого класса
Класс маршрутизации в PHP: варианты реализации
Наиболее эффективное решение для маршрутизации в PHP - создание универсального класса Router, который поддерживает HTTP-методы, динамические параметры URL, именованные маршруты и middleware. Такой подход позволяет централизованно управлять всеми маршрутами и легко расширять функциональность.
<?php
class Router {
private array $routes = [];
private array $middlewares = [];
public function add(string $method, string $pattern, callable $handler, string $name = null): void {
$this->routes[] = [
'method' => strtoupper($method),
'pattern' => $this->convertToRegex($pattern),
'handler' => $handler,
'name' => $name
];
}
private function convertToRegex(string $pattern): string {
$regex = preg_replace('/\{([a-zA-Z_]+)\}/', '(?P<$1>[^/]+)', $pattern);
return '#^' . $regex . '$#';
}
public function addMiddleware(callable $middleware): void {
$this->middlewares[] = $middleware;
}
public function dispatch(string $method, string $uri): void {
$method = strtoupper($method);
$uri = parse_url($uri, PHP_URL_PATH);
foreach ($this->routes as $route) {
if ($route['method'] === $method && preg_match($route['pattern'], $uri, $matches)) {
$params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
$request = ['method' => $method, 'uri' => $uri, 'params' => $params];
foreach ($this->middlewares as $middleware) {
$result = $middleware($request);
if ($result === false) return;
}
call_user_func($route['handler'], $params);
return;
}
}
http_response_code(404);
echo '404 Not Found';
}
}Ru index php route account order (маршрут заказа аккаунта (ru))
Пояснение шагов:
- add() - регистрирует маршрут с указанием метода, шаблона URL, обработчика и необязательного имени.
- convertToRegex() - преобразует шаблоны вида
{id}в именованные группы регулярного выражения. - addMiddleware() - добавляет промежуточное ПО, которое выполняется перед обработчиком.
- dispatch() - сопоставляет текущий запрос с маршрутами и запускает цепочку middleware, затем обработчик.
Почему маршруты не срабатывают при наличии query-параметров?
Использование parse_url($uri, PHP_URL_PATH) отсекает строку запроса, поэтому маршруты сопоставляются только с путём. Типичная ошибка - передача полного URI без обработки.
Как реализовать простейшую маршрутизацию без классов?
<?php
$uri = $_SERVER['REQUEST_URI'];
$method = $_SERVER['REQUEST_METHOD'];
switch (true) {
case $method === 'GET' && $uri === '/':
echo 'Home';
break;
case $method === 'GET' && preg_match('#^/user/(\d+)$#', $uri, $m):
echo 'User ID: ' . $m[1];
break;
default:
http_response_code(404);
echo 'Not Found';
}Php route product manufacturer (маршрут php для продукта производителя)
Этот подход прост для понимания, но приводит к разрастанию кода при большом числе маршрутов. Проблемы: отсутствие повторного использования, сложность с middleware, низкая гибкость.
Как избежать дублирования кода при обработке однотипных маршрутов?
Решение - вынести общую логику в функции или сразу перейти к классу Router. В приведённом примере каждый маршрут обрабатывается отдельно, что усложняет сопровождение.
Как обрабатывать динамические URL с параметрами?
Использование регулярных выражений с именованными группами позволяет гибко извлекать параметры из URL. Пример:
<?php
$pattern = '#^/post/(?P<year>\d{4})/(?P<slug>[a-z0-9-]+)$#';
$uri = '/post/2025/hello-world';
if (preg_match($pattern, $uri, $matches)) {
$params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
print_r($params);
}Class route php (класс маршрутизации в php)
Array ( [year] => 2025 [slug] => hello-world )
Php route login (маршрут входа в php)
Однако поддержка сложных шаблонов вручную увеличивает вероятность ошибок. Рекомендуется автоматизировать преобразование, как в основном решении.
Почему регулярные выражения могут не сработать из-за спецсимволов в URL?
Если в шаблоне используются символы вроде . или ?, их нужно экранировать. Лучше применять preg_quote() для статических частей, а динамические сегменты оставлять без экранирования.
Как организовать цепочку обработчиков для маршрутов?
Шаблон Chain of Responsibility позволяет последовательно передавать запрос через несколько обработчиков, каждый из которых может обработать запрос или передать дальше. Реализация:
<?php
abstract class Handler {
protected ?Handler $next = null;
public function setNext(Handler $handler): Handler {
$this->next = $handler;
return $handler;
}
abstract public function handle(string $uri): ?string;
}
class RouteHandler extends Handler {
private array $routes;
public function __construct(array $routes) {
$this->routes = $routes;
}
public function handle(string $uri): ?string {
foreach ($this->routes as $pattern => $action) {
if (preg_match($pattern, $uri)) {
return $action($uri);
}
}
return $this->next ? $this->next->handle($uri) : null;
}
}Routing api php (маршрутизация api в php)
Такой подход удобен для создания слоёв безопасности или логирования, но избыточен для простой маршрутизации. Проблема - сложность отладки и производительность при длинных цепях.
Как избежать бесконечного цикла при передаче запроса?
Необходимо гарантировать, что последний обработчик возвращает значение (например, 404), иначе цепочка может никогда не завершиться.
Когда использовать готовые решения вместо самописного класса?
Библиотеки вроде FastRoute или AltoRouter предоставляют оптимизированную маршрутизацию с поддержкой кэширования, что критично для высоконагруженных проектов. Пример со FastRoute:
<?php
use FastRoute\RouteCollector;
$dispatcher = FastRoute\simpleDispatcher(function(RouteCollector $r) {
$r->addRoute('GET', '/user/{id:\d+}', 'get_user');
});
$routeInfo = $dispatcher->dispatch($httpMethod, $uri);
switch ($routeInfo[0]) {
case FastRoute\Dispatcher::FOUND:
$handler = $routeInfo[1];
$vars = $routeInfo[2];
break;
case FastRoute\Dispatcher::NOT_FOUND:
// 404
break;
case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
// 405
break;
}
Готовые решения экономят время, но требуют изучения документации и могут накладывать ограничения на структуру проекта. Выбор между самописным классом и библиотекой зависит от масштаба задачи и необходимости тонкой настройки.
Почему при использовании FastRoute маршруты с одинаковым паттерном не переопределяются?
FastRoute запрещает дублирование маршрутов; при попытке добавить существующий маршрут возникает исключение. Это предотвращает случайные перезаписи, но может стать неожиданностью для новичков.
Расширенные примеры и редкие сценарии использования
Полноценная реализация Router с группировкой и префиксами
<?php
class Router {
private array $routes = [];
private array $middlewares = [];
private string $prefix = '';
private array $groupMiddleware = [];
public function group(string $prefix, callable $callback, array $middlewares = []): void {
$previousPrefix = $this->prefix;
$previousMiddleware = $this->groupMiddleware;
$this->prefix .= $prefix;
$this->groupMiddleware = array_merge($this->groupMiddleware, $middlewares);
$callback($this);
$this->prefix = $previousPrefix;
$this->groupMiddleware = $previousMiddleware;
}
public function add(string $method, string $pattern, callable $handler, string $name = null): void {
$fullPattern = $this->prefix . $pattern;
$this->routes[] = [
'method' => strtoupper($method),
'pattern' => $this->convertToRegex($fullPattern),
'handler' => $handler,
'name' => $name,
'middlewares' => $this->groupMiddleware
];
}
private function convertToRegex(string $pattern): string {
$regex = preg_replace('/\{([a-zA-Z_]+)\}/', '(?P<$1>[^/]+)', $pattern);
$regex = preg_replace('/\{([a-zA-Z_]+):([^}]+)\}/', '(?P<$1>$2)', $regex);
return '#^' . $regex . '$#';
}
public function dispatch(string $method, string $uri): void {
$method = strtoupper($method);
$uri = parse_url($uri, PHP_URL_PATH);
foreach ($this->routes as $route) {
if ($route['method'] !== $method) continue;
if (!preg_match($route['pattern'], $uri, $matches)) continue;
$params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
$request = ['method' => $method, 'uri' => $uri, 'params' => $params];
// Выполнение middleware группы и глобальных
foreach ($route['middlewares'] as $mw) {
if ($mw($request) === false) return;
}
foreach ($this->middlewares as $mw) {
if ($mw($request) === false) return;
}
call_user_func($route['handler'], $params);
return;
}
http_response_code(404);
echo json_encode(['error' => 'Route not found']);
}
}
Этот класс поддерживает вложенные группы с собственными middleware и префиксами, а также позволяет задавать типы параметров через двоеточие (например, {id:\d+}).
Маршрутизация для REST API с разными форматами ответа
<?php
$router = new Router();
$router->add('GET', '/api/users', function($params) {
header('Content-Type: application/json');
echo json_encode(['users' => ['Alice', 'Bob']]);
}, 'users.list');
$router->add('GET', '/api/users/{id}', function($params) {
header('Content-Type: application/json');
echo json_encode(['user_id' => $params['id']]);
}, 'users.show');
$router->add('POST', '/api/users', function($params) {
$input = json_decode(file_get_contents('php://input'), true);
// Сохранение пользователя
echo json_encode(['status' => 'created']);
}, 'users.create');
// Использование именованных маршрутов для генерации URL
function route(string $name, array $params = []): string {
global $router;
// В упрощённом виде - обычно имя хранится в отдельном массиве
return '/api/users/' . ($params['id'] ?? '');
}
echo route('users.show', ['id' => 42]); // /api/users/42
/api/users/42
Пример демонстрирует, как использовать роутер для типичного REST эндпоинта с JSON и именованными маршрутами.
Обработка middleware для аутентификации
<?php
$router->addMiddleware(function($request) {
$token = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if (empty($token)) {
http_response_code(401);
echo json_encode(['error' => 'Unauthorized']);
return false; // останавливаем цепочку
}
return true;
});
$router->add('GET', '/admin', function($params) {
echo 'Admin panel';
});
Middleware проверяет токен перед выполнением любого маршрута. Если вернуть false, обработка прекращается.
Кэширование списка маршрутов для ускорения
<?php
class Router {
private array $routes = [];
private ?array $cachedRoutes = null;
public function add(string $method, string $pattern, callable $handler): void {
$this->routes[] = [
'method' => strtoupper($method),
'pattern' => $this->convertToRegex($pattern),
'handler' => $handler
];
}
public function getRoutesForCache(): array {
return $this->routes;
}
public function setRoutesFromCache(array $routes): void {
$this->cachedRoutes = $routes;
}
public function dispatch(string $method, string $uri): void {
$routes = $this->cachedRoutes ?? $this->routes;
// ... остальная логика
}
}
// Пример кэширования в файл
$router = new Router();
// ... регистрация маршрутов
file_put_contents('routes.cache', serialize($router->getRoutesForCache()));
// При следующем запросе
$cached = unserialize(file_get_contents('routes.cache'));
$router->setRoutesFromCache($cached);
Этот подход ускоряет работу, сокращая время на компиляцию регулярных выражений при каждом запросе.