Представьте, что ваш сайт — это город, а роутинг — его система дорог и указателей. Вместо громоздких адресов вроде site.ru/page.php?id=42&category=books вы получаете элегантные пути: site.ru/books/42. Это не просто красиво — это фундамент современного веба. Давайте разберемся, как создать свою систему маршрутизации на чистом PHP, понимая каждый кирпичик этой архитектуры.
Что такое роутинг и зачем он нужен?
Роутинг (маршрутизация) — это механизм, который сопоставляет URL-адреса с конкретными обработчиками в вашем приложении. Когда пользователь заходит на /about, система определяет, какой код должен выполниться, чтобы показать страницу "О нас".
Ключевая выгода: ЧПУ (человеко-понятные URL) улучшают SEO, юзабилити и структуру проекта. Поисковые системы лучше индексируют такие адреса, а пользователям проще их запоминать и делиться ими.
Три подхода к реализации
1. Самый простой: через .htaccess (Apache)
Если ваш хостинг использует Apache, можно перенаправлять все запросы на единую точку входа:
.htaccess:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?route=$1 [QSA,L]
index.php:
$route = $_GET['route'] ?? 'home';
switch($route) {
case 'about':
include 'pages/about.php';
break;
case 'contact':
include 'pages/contact.php';
break;
default:
include 'pages/home.php';
}
Этот метод отлично подходит для небольших проектов, но быстро становится громоздким при росте количества страниц. Уже на 10-15 маршрутах поддержка switch/case превращается в кошмар.
2. Продвинутый: регулярные выражения и параметры
Здесь мы создаем массив маршрутов, где ключ — регулярное выражение, а значение — обработчик:
$routes = [
'~^/blog/(\d+)$~' => function($id) {
// Показываем статью с ID = $id
echo "Статья #$id";
},
'~^/catalog/([a-z]+)/(\d+)$~' => function($category, $productId) {
// Товар $productId в категории $category
echo "Товар #$productId в категории $category";
}
];
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
foreach ($routes as $pattern => $handler) {
if (preg_match($pattern, $uri, $matches)) {
array_shift($matches); // Убираем полное совпадение
call_user_func_array($handler, $matches);
return;
}
}
// Если ничего не найдено
http_response_code(404);
include '404.php';
3. Профессиональный: ООП-роутер с поддержкой методов
Для серьезных проектов создаем класс Router, который умеет:
- Регистрировать маршруты для разных HTTP-методов (GET, POST)
- Парсить динамические параметры (
{id},{slug}) - Группировать маршруты по префиксам
- Кэшировать скомпилированные регулярки для производительности
Пример структуры класса:
class Router {
private $routes = [];
public function get($path, $handler) {
$this->addRoute('GET', $path, $handler);
}
public function post($path, $handler) {
$this->addRoute('POST', $path, $handler);
}
private function addRoute($method, $path, $handler) {
// Преобразуем {id} в регулярку (\\d+)
$pattern = preg_replace('/\{(\w+)\}/', '(?P<$1>[^/]+)', $path);
$this->routes[] = [
'method' => $method,
'pattern' => '#^' . $pattern . '$#',
'handler' => $handler
];
}
public function dispatch() {
$method = $_SERVER['REQUEST_METHOD'];
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
foreach ($this->routes as $route) {
if ($route['method'] !== $method) continue;
if (preg_match($route['pattern'], $uri, $matches)) {
$params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
call_user_func_array($route['handler'], $params);
return;
}
}
$this->notFound();
}
}
Практический пример: блог с роутингом
Создаем простой блог с тремя типами страниц:
$router = new Router();
// Главная
$router->get('/', function() {
include 'views/home.php';
});
// Список статей
$router->get('/blog', function() {
$posts = getRecentPosts(10);
include 'views/blog/list.php';
});
// Конкретная статья
$router->get('/blog/{slug}', function($slug) {
$post = getPostBySlug($slug);
if (!$post) {
header('HTTP/1.0 404 Not Found');
return;
}
include 'views/blog/single.php';
});
// Обработка формы комментария
$router->post('/blog/{slug}/comment', function($slug) {
$comment = $_POST['comment'];
addComment($slug, $comment);
header("Location: /blog/$slug");
});
$router->dispatch();
Важный нюанс: Всегда проверяйте входные данные из URL! Параметр {slug} может содержать только буквы, цифры и дефисы. Используйте preg_match('/^[a-z0-9-]+$/', $slug) для валидации.
Оптимизация и лучшие практики
- Кэширование маршрутов: На продакшене компилируйте массив маршрутов в PHP-файл, чтобы не пересоздавать его каждый запрос
- Middleware: Добавьте промежуточные обработчики для аутентификации, логирования, CORS
- Обработка ошибок: Создайте красивые страницы 404, 403, 500 с редиректом на главную
- Reverse routing: Реализуйте функцию
route('blog.show', ['slug' => 'my-post']), которая генерирует URL по имени маршрута
Альтернативы: готовые решения
Если не хотите писать роутер с нуля:
- Slim Framework: Микрофреймворк с элегантным роутингом
- Laravel: Промышленный фреймворк с мощной системой маршрутизации
- Symfony Routing Component: Можно использовать отдельно от фреймворка
- FastRoute: Библиотека только для роутинга с высокой производительностью
FAQ: Часто задаваемые вопросы
Какой подход выбрать для стартапа?
Начните с простого .htaccess + switch, но сразу проектируйте архитектуру для миграции на ООП-роутер. Первые 2-3 месяца простого решения хватит, но потом понадобится структура.
Нужно ли знать регулярные выражения для роутинга?
Да, но на базовом уровне. Достаточно понимать \d+ (цифры), [a-z]+ (буквы), [^/]+ (всё кроме слеша). В готовых фреймворках синтаксис проще: /user/{id:\d+}.
Как обрабатывать вложенные маршруты типа /admin/users/edit/5?
Используйте группировку:
$router->group('/admin', function($router) {
$router->get('/users', 'UserController@index');
$router->get('/users/edit/{id}', 'UserController@edit');
});
Роутинг влияет на SEO?
Критически! Поисковики любят плоскую структуру и ключевые слова в URL. /blog/kak-sdelat-routing-v-php индексируется лучше, чем /blog.php?id=42.
Можно ли сделать роутинг без mod_rewrite?
Да, через FallbackResource в Apache или try_files в Nginx. Но проще всего — настройка через .htaccess как в примерах выше.