Создание маршрута заказа аккаунта с поддержкой мультиязычности
Реализация маршрута заказа аккаунта с языковым префиксом
Рассмотрим задачу: на сайте с поддержкой русского языка необходимо обрабатывать URL вида /ru/index.php?route=account/order или ЧПУ вида /ru/account/order. Требуется написать систему роутинга, которая будет направлять запрос к соответствующему контроллеру.
Как создать собственный роутер с поддержкой языкового префикса и параметров?
Наиболее гибкое и контролируемое решение – написать класс Router вручную. Это позволяет точно настроить обработку URL и не зависеть от внешних библиотек.
// Класс Router
class Router {
private array $routes = [];
public function add(string $pattern, callable $handler): void {
// Преобразуем шаблон в регулярное выражение
$pattern = preg_replace('/\{([a-z]+)\}/', '(?P<$1>[^/]+)', $pattern);
$this->routes[] = [
'pattern' => '/^' . str_replace('/', '\/', $pattern) . '$/',
'handler' => $handler
];
}
public function dispatch(string $uri): void {
$uri = parse_url($uri, PHP_URL_PATH);
foreach ($this->routes as $route) {
if (preg_match($route['pattern'], $uri, $matches)) {
// Извлекаем только именованные параметры
$params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
call_user_func($route['handler'], $params);
return;
}
}
// 404
http_response_code(404);
echo 'Страница не найдена';
}
}
// Использование
$router = new Router();
$router->add('/{lang}/account/order', function($params) {
echo 'Язык: ' . $params['lang'] . ' – Страница заказа аккаунта';
});
$router->add('/{lang}/account/login', function($params) {
echo 'Язык: ' . $params['lang'] . ' – Вход в аккаунт';
});
$router->dispatch($_SERVER['REQUEST_URI']);Ru index php route account order (маршрут заказа аккаунта (ru))
Пояснение шагов:
- Метод add принимает шаблон с плейсхолдерами в фигурных скобках и заменяет их на именованные группы в регулярном выражении.
- Метод dispatch получает URI, отбрасывает параметры запроса и последовательно проверяет маршруты.
- При совпадении вызывается обработчик с массивом параметров.
- Если маршрут не найден – возвращается ошибка 404.
Типичные проблемы и их решение:
- Порядок маршрутов: Если один шаблон является подстрокой другого, менее специфичный маршрут может перехватить запрос. Решение – размещать более конкретные маршруты выше.
- Символы в URI: Регулярное выражение может не учитывать слеши или специальные символы. Необходимо экранировать слеши и использовать правильные классы символов.
- Производительность: При большом количестве маршрутов последовательный поиск может быть медленным. Решение – использовать статический анализ или кэшировать скомпилированные маршруты.
Как реализовать роутинг через обычный switch/case для фиксированных маршрутов?
Для простых сайтов с небольшим числом статических маршрутов можно обойтись конструкцией switch. Это самый прямолинейный способ.
$uri = $_SERVER['REQUEST_URI'];
$path = parse_url($uri, PHP_URL_PATH);
switch ($path) {
case '/ru/account/order':
echo 'Заказ аккаунта (рус)';
break;
case '/en/account/order':
echo 'Order account (en)';
break;
default:
http_response_code(404);
echo 'Страница не найдена';
break;
}Php route product manufacturer (маршрут php для продукта производителя)
Цель – быстрое развертывание без дополнительных классов. Случаи использования – прототипы, тестовые проекты.
Проблемы:
- Отсутствие динамических параметров – каждый маршрут жестко задан.
- При добавлении новых языков или страниц код разрастается.
- Невозможность обработки ЧПУ с переменными частями.
Как применить регулярные выражения напрямую без класса?
Можно использовать ассоциативный массив шаблонов и обработчиков, перебирая его.
$routes = [
'#^/([a-z]{2})/account/order$#' => function($m) { echo "Язык: $m[1] – заказ"; },
'#^/([a-z]{2})/account/login$#' => function($m) { echo "Язык: $m[1] – вход"; },
];
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$found = false;
foreach ($routes as $pattern => $handler) {
if (preg_match($pattern, $uri, $matches)) {
$handler($matches);
$found = true;
break;
}
}
if (!$found) {
http_response_code(404);
echo 'Не найдено';
}Class route php (класс маршрутизации в php)
Этот подход компактнее switch, но всё ещё не структурирован. Подходит для небольших проектов с известными шаблонами.
Проблемы:
- Трудно поддерживать сложные маршруты с несколькими параметрами.
- Отсутствует проверка HTTP методов (GET, POST).
- Код повторяется для разных маршрутов.
Как использовать библиотеку AltoRouter для организации маршрута account/order?
AltoRouter – популярная библиотека с простым API. Позволяет задавать именованные маршруты, поддерживает фильтры.
// Установка: composer require altorouter/altorouter
require 'vendor/autoload.php';
$router = new AltoRouter();
// Добавляем маршруты
$router->map('GET', '/[a:lang]/account/order', function($lang) {
echo "Язык: $lang – заказ";
});
$router->map('GET', '/[a:lang]/account/order/[i:id]', function($lang, $id) {
echo "Язык: $lang, ID: $id";
});
// Обработка
$match = $router->match();
if ($match && is_callable($match['target'])) {
call_user_func_array($match['target'], $match['params']);
} else {
header('HTTP/1.0 404 Not Found');
echo '404';
}Php route login (маршрут входа в php)
Цель – использование проверенного решения с минимальным кодом. Подходит для проектов средней сложности.
Проблемы:
- Необходимость установки через Composer.
- Ограниченная гибкость в определении сложных шаблонов.
- При большом количестве маршрутов может быть менее производительным, чем FastRoute.
Как реализовать роутинг с помощью FastRoute для высокой производительности?
FastRoute – одна из самых быстрых библиотек, использует скомпилированные регулярные выражения. Рекомендуется для высоконагруженных проектов.
// Установка: composer require nikic/fast-route
require 'vendor/autoload.php';
$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
$r->addRoute('GET', '/{lang:[a-z]{2}}/account/order', function($lang) {
echo "Язык: $lang – заказ";
});
$r->addRoute('GET', '/{lang}/account/order/{id:\d+}', function($lang, $id) {
echo "Язык: $lang, ID: $id";
});
});
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$routeInfo = $dispatcher->dispatch($_SERVER['REQUEST_METHOD'], $uri);
switch ($routeInfo[0]) {
case FastRoute\Dispatcher::FOUND:
$handler = $routeInfo[1];
$vars = $routeInfo[2];
$handler(...$vars);
break;
case FastRoute\Dispatcher::NOT_FOUND:
http_response_code(404);
echo '404 Not Found';
break;
case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
http_response_code(405);
echo '405 Method Not Allowed';
break;
}Routing api php (маршрутизация api в php)
Цель – максимальная скорость и поддержка сложных маршрутов. Используется в крупных проектах, фреймворках.
Проблемы:
- Требуется установка через Composer.
- Меньше встроенных функций по сравнению с AltoRouter (например, нет именованных маршрутов).
- Необходимость самостоятельно формировать ответы.
Как организовать роутинг в стиле MVC с использованием Front Controller?
В этом подходе все запросы направляются в единую точку входа (index.php), где роутер разбирает URL и вызывает соответствующий контроллер. Подходит для фреймворков.
// index.php
require 'Router.php';
$router = new Router();
$router->add('GET', '/{lang}/account/order', 'AccountController@order');
$router->add('GET', '/{lang}/account/login', 'AccountController@login');
$result = $router->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);
// Внутри Router создается экземпляр AccountController и вызывается метод order($lang)
Подробнее: Router парсит URL, определяет контроллер и метод, создает объект и вызывает метод с параметрами. Это основа многих фреймворков (Laravel, Symfony).
Проблемы:
- Необходимо реализовать автозагрузку классов.
- Требуется продумать соглашения об именовании контроллеров.
- Сложность с внедрением зависимостей.
Расширенные примеры реализации роутинга для маршрута account/order
Пример собственного роутера с поддержкой HTTP методов и middleware
Реализуем роутер, который учитывает метод запроса и позволяет добавить промежуточные функции (middleware) для проверки языкового префикса.
class Router {
private array $routes = [];
public function add(string $method, string $pattern, callable $handler, array $middleware = []): void {
$pattern = preg_replace('/\{([a-z]+)\}/', '(?P<$1>[^/]+)', $pattern);
$this->routes[] = [
'method' => strtoupper($method),
'pattern' => '/^' . str_replace('/', '\/', $pattern) . '$/',
'handler' => $handler,
'middleware' => $middleware
];
}
public function dispatch(string $method, string $uri): void {
$uri = parse_url($uri, PHP_URL_PATH);
$method = strtoupper($method);
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);
// Выполняем middleware
foreach ($route['middleware'] as $mw) {
if (call_user_func($mw, $params) === false) {
return; // middleware прервало выполнение
}
}
call_user_func($route['handler'], $params);
return;
}
}
http_response_code(404);
echo 'Не найдено';
}
}
// Middleware для проверки языка
$langMiddleware = function($params) {
$allowed = ['ru', 'en'];
if (!isset($params['lang']) || !in_array($params['lang'], $allowed)) {
http_response_code(400);
echo 'Недопустимый язык';
return false;
}
return true;
};
$router = new Router();
$router->add('GET', '/{lang}/account/order', function($p) {
echo "Заказ для языка: {$p['lang']}";
}, [$langMiddleware]);
$router->dispatch('GET', '/ru/account/order');
Результат выполнения:
Заказ для языка: ru
Если указать неверный язык, например /fr/account/order, middleware вернет false и будет выведено сообщение об ошибке 400.
Пример с FastRoute и автоматической генерацией URL
FastRoute не предоставляет генерацию URL, но можно создать вспомогательную функцию, используя обратное преобразование шаблона. Реализуем простой генератор на основе имени маршрута.
// Определим маршруты с именами
$routes = [
'order' => ['method' => 'GET', 'pattern' => '/{lang}/account/order', 'handler' => function($lang) { echo "Order $lang"; }],
'order_with_id' => ['method' => 'GET', 'pattern' => '/{lang}/account/order/{id:\d+}', 'handler' => function($lang, $id) { echo "Order $lang $id"; }],
];
// Функция генерации URL
function generateUrl(string $name, array $params = []): string {
global $routes;
if (!isset($routes[$name])) {
throw new Exception('Route not found');
}
$url = $routes[$name]['pattern'];
foreach ($params as $key => $value) {
$url = str_replace('{' . $key . ':?[^/]+?}', $value, $url);
$url = str_replace('{' . $key . '}', $value, $url);
}
return $url;
}
// Использование FastRoute
$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) use ($routes) {
foreach ($routes as $name => $route) {
$r->addRoute($route['method'], $route['pattern'], $route['handler']);
}
});
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$routeInfo = $dispatcher->dispatch('GET', $uri);
if ($routeInfo[0] === FastRoute\Dispatcher::FOUND) {
$handler = $routeInfo[1];
$vars = $routeInfo[2];
$handler(...$vars);
}
// Пример генерации ссылки
echo 'Ссылка на заказ: ' . generateUrl('order', ['lang' => 'ru']);
Результат:
Ссылка на заказ: /ru/account/order
Такой подход позволяет централизованно управлять URL и избегать жесткого прописывания путей в шаблонах.
Пример с AltoRouter и именованными маршрутами для мультиязычного сайта
AltoRouter позволяет задать имя маршруту и затем генерировать URL по имени. Это удобно для создания ссылок на странице.
require 'vendor/autoload.php';
$router = new AltoRouter();
// Добавляем маршрут с именем 'account_order'
$router->map('GET', '/[a:lang]/account/order', function($lang) {
echo "Order page for $lang";
}, 'account_order');
// Генерация URL
$urlRu = $router->generate('account_order', ['lang' => 'ru']);
$urlEn = $router->generate('account_order', ['lang' => 'en']);
echo 'Ссылка на русском: ' . $urlRu . '
';
echo 'Ссылка на английском: ' . $urlEn . '
';
// Выполнение роутинга
$match = $router->match();
if ($match && is_callable($match['target'])) {
call_user_func_array($match['target'], $match['params']);
}
Результат:
Ссылка на русском: /ru/account/order
Ссылка на английском: /en/account/order
При запуске скрипта по адресу /ru/account/order будет выведено: "Order page for ru".
Пример обработки опциональных параметров (например, id) через регулярные выражения
Иногда параметр может отсутствовать. Создадим роутер, который поддерживает необязательные сегменты.
$routes = [
'#^/([a-z]{2})/account/order(?:/(\d+))?$#' => function($m) {
$lang = $m[1];
$id = isset($m[2]) ? $m[2] : null;
echo "Язык: $lang. " . ($id ? "ID заказа: $id" : "Все заказы");
}
];
$uri = '/ru/account/order/42';
preg_match(key($routes), $uri, $matches);
$routes[key($routes)]($matches); // Вывод: Язык: ru. ID заказа: 42
$uri2 = '/ru/account/order';
preg_match(key($routes), $uri2, $matches);
$routes[key($routes)]($matches); // Вывод: Язык: ru. Все заказы
Результат:
Для URI /ru/account/order/42: Язык: ru. ID заказа: 42
Для URI /ru/account/order: Язык: ru. Все заказы
Использование нежадных квантификаторов и групп без захвата позволяет гибко обрабатывать опциональные части URL.