Единая точка входа: обработка действий через index.php
Организация маршрутизации через index.php и параметр action
Основной подход: единая точка входа с динамическим вызовом контроллера
В веб-приложениях с архитектурой Front Controller файл index.php обрабатывает все входящие запросы. Параметр action в URL (например, index.php?action=login) определяет, какой именно код выполнить. Ниже показано эффективное решение, сочетающее безопасность и гибкость.
// index.php
$action = $_GET['action'] ?? 'default';
$allowedActions = ['login', 'register', 'logout', 'profile', 'default'];
if (!in_array($action, $allowedActions, true)) {
http_response_code(404);
exit('Action not found');
}
$controllerClass = 'Controllers\\' . ucfirst($action) . 'Controller';
$controller = new $controllerClass();
$controller->handle();
Index php action (действия и маршруты)
Пояснение шагов:
- Извлекается параметр
actionиз строки запроса; если он отсутствует, используется 'default'. - Проверка по белому списку
$allowedActionsпредотвращает вызов произвольных методов и повышает безопасность. - Имя класса формируется с учётом пространства имён и передаётся в
new. Вызовhandle()единообразен для всех контроллеров.
Возможные проблемы и их решения
Проблема: Если в URL передать недопустимый символ, ucfirst может сгенерировать некорректное имя класса. Решение: фильтрация с помощью preg_replace('/[^a-zA-Z0-9]/', '', $action) перед поиском в белом списке.
Проблема: Много action ведёт к большому числу файлов контроллеров. Решение: группировка action по модулям (например, admin/users) или использование маршрутизатора более высокого уровня.
Проблема: Повторение кода загрузки и вызова для каждого action. Решение: разместить логику вызова в отдельном классе Dispatcher или использовать готовую реализацию PSR-11.
Как реализовать маршрутизацию без условных операторов, используя ассоциативный массив?
Можно сопоставить строку action с анонимной функцией или массивом [класс, метод]. Это упрощает добавление новых маршрутов.
$routes = [
'login' => ['Controllers\AuthController', 'login'],
'register' => ['Controllers\AuthController', 'register'],
'logout' => ['Controllers\AuthController', 'logout'],
'default' => ['Controllers\HomeController', 'index'],
];
$action = $_GET['action'] ?? 'default';
if (isset($routes[$action])) {
[$class, $method] = $routes[$action];
$controller = new $class();
$controller->$method();
} else {
// 404
}
Преимущество: легче читать и поддерживать. Недостаток: при большом количестве маршрутов массив становится громоздким, возможны коллизии имён.
Как обрабатывать URL с динамическими сегментами, например /user/42?
Вместо одного параметра action используют разбор пути через $_SERVER['REQUEST_URI'] и регулярные выражения.
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$uri = rtrim($uri, '/');
$routes = [
'/user/(\d+)' => ['Controllers\UserController', 'show'],
'/article/([a-z0-9-]+)' => ['Controllers\ArticleController', 'view'],
];
$found = false;
foreach ($routes as $pattern => $handler) {
if (preg_match('#^' . $pattern . '$#', $uri, $matches)) {
[$class, $method] = $handler;
$controller = new $class();
$controller->$method($matches[1]);
$found = true;
break;
}
}
if (!$found) {
http_response_code(404);
}
Примечание: регулярные выражения нужно тщательно экранировать. Ошибка в паттерне может лишить сайт функциональности.
Типичная ошибка: забывают экранировать слэши или использовать якоря ^ и $, что приводит к ложным срабатываниям.
Как сделать маршрутизацию с автозагрузкой классов по соглашению об именах?
Вместо белого списка можно полагаться на строгий шаблон: префикс namespace + имя action + суффикс Controller. Если класс не найден – 404.
$action = preg_replace('/[^a-zA-Z0-9_]/', '', $_GET['action'] ?? 'index');
$class = 'Controllers\\' . ucfirst($action) . 'Controller';
if (class_exists($class)) {
$controller = new $class();
$controller->handle();
} else {
http_response_code(404);
}
Опасность: злоумышленник может попытаться вызвать любой существующий класс системы, если не настроен autoload строго по пространству имён. Рекомендуется комбинировать с проверкой интерфейса или абстрактного класса.
Как подключить готовый микрофреймворк для упрощения маршрутизации?
Популярные решения – Slim, Flight, Laminas Mezzio. Они предоставляют декларативный API и обрабатывают соответствие маршрутов автоматически.
// Slim 4
use Slim\Factory\AppFactory;
$app = AppFactory::create();
$app->get('/users/{id}', function ($request, $response, $args) {
$id = $args['id'];
// обработка
return $response;
});
$app->run();
Плюсы: встроенная обработка HTTP методов, middleware, PSR-7 совместимость. Минусы: зависимость от стороннего кода, избыточность для простых проектов.
Цели и случаи использования каждого варианта
| Вариант | Когда использовать |
|---|---|
| Switch / белый список | Небольшие приложения с фиксированным набором action (менее 10). |
| Ассоциативный массив | Проекты с несколькими контроллерами, где важно отделить маршруты от логики. |
| Регулярные выражения | Необходимы ЧПУ (человеко-понятные URL) и переменные сегменты. |
| Автозагрузка по соглашению | Большие проекты с хорошо организованной файловой структурой (например, Laravel-подобная). |
| Микрофреймворк | Быстрое прототипирование или создание API с минимальным кодом. |
Расширенные примеры маршрутизации с index.php и action
Ниже приведены подробные примеры, демонстрирующие нестандартные или комплексные сценарии.
Пример 1: Интеграция middleware для проверки аутентификации
Маршрутизатор может вызывать промежуточные слои перед выполнением основного действия. Реализовано через цепочку вызовов.
// index.php
$middleware = [
'dashboard' => ['auth'],
'profile' => ['auth'],
'login' => [],
];
$action = $_GET['action'] ?? 'default';
$action = preg_replace('/[^a-zA-Z0-9_]/', '', $action);
// Применение middleware
$middlewares = $middleware[$action] ?? [];
foreach ($middlewares as $mw) {
$mwClass = 'Middleware\\' . ucfirst($mw) . 'Middleware';
(new $mwClass())->handle();
}
// Вызов контроллера
$class = 'Controllers\\' . ucfirst($action) . 'Controller';
if (class_exists($class)) {
(new $class())->handle();
} else {
http_response_code(404);
}
Результат: Для action=dashboard перед выполнением вызывается AuthMiddleware, который проверяет сессию. Если пользователь не авторизован – перенаправление на login.
Пример 2: RESTful маршруты с комбинацией action и дополнительным параметром method
Для реализации CRUD можно использовать action как ресурс, а method – как операцию (create, read, update, delete).
// index.php?action=users&method=create&name=John
$action = $_GET['action'] ?? '';
$method = $_GET['method'] ?? 'list';
$restRoutes = [
'users' => [
'list' => ['Controllers\UsersController', 'listAll'],
'create' => ['Controllers\UsersController', 'create'],
'read' => ['Controllers\UsersController', 'read'],
'update' => ['Controllers\UsersController', 'update'],
'delete' => ['Controllers\UsersController', 'delete'],
],
'articles' => [
// аналогично
],
];
if (isset($restRoutes[$action][$method])) {
[$class, $func] = $restRoutes[$action][$method];
$controller = new $class();
$controller->$func($_GET);
} else {
http_response_code(400);
}
Результат: URL index.php?action=users&method=create&name=John вызовет UsersController::create(['name'=>'John']).
Пример 3: Динамическая маршрутизация с поддержкой вложенных путей
Параметр action может содержать слэши для обозначения вложенности, например index.php?action=admin/users/edit&id=5.
$action = $_GET['action'] ?? 'home';
$segments = explode('/', $action);
// Последний сегмент – это метод, все предыдущие – путь к контроллеру
$method = array_pop($segments);
$namespace = 'Controllers\\' . implode('\\', array_map('ucfirst', $segments));
$class = $namespace . 'Controller';
if (class_exists($class) && method_exists($class, $method)) {
$controller = new $class();
$controller->$method($_GET);
} else {
http_response_code(404);
}
Результат: action=admin/users/edit приводит к вызову Controllers\Admin\UsersController::edit($_GET). Требуется автозагрузка с PSR-4.
Пример 4: Использование PSR-7 запросов и ответов совместно с маршрутизатором
Современные фреймворки работают с объектами Request и Response. Ниже фрагмент реализации без внешних зависимостей.
// index.php
require 'vendor/autoload.php';
use Laminas\Diactoros\ServerRequestFactory;
use Laminas\Diactoros\Response;
$request = ServerRequestFactory::fromGlobals();
$uri = $request->getUri()->getPath();
$routes = [
'/home' => ['Controllers\HomeController', 'index'],
'/user/(\d+)' => ['Controllers\UserController', 'show'],
];
foreach ($routes as $pattern => $handler) {
if (preg_match('#^' . $pattern . '$#', $uri, $matches)) {
[$class, $method] = $handler;
$controller = new $class();
$response = $controller->$method($request, $matches[1]);
// отправка ответа
http_response_code($response->getStatusCode());
echo $response->getBody();
exit;
}
}
// 404
$response = new Response\EmptyResponse(404);
http_response_code(404);
Результат: URL /user/42 вызовет UserController::show($request, '42'), возвращающий PSR-7 Response.
Пример 5: Кеширование маршрутов для ускорения
При большом количестве маршрутов повторный разбор каждого запроса замедляет работу. Можно сериализовать массив маршрутов в файл.
// cache_routes.php
$routes = [
'/user/(\d+)' => ['Controllers\UserController', 'show'],
// ...
];
file_put_contents(__DIR__ . '/routes_cache.php', '<?php return ' . var_export($routes, true) . ';');
// index.php
if (file_exists(__DIR__ . '/routes_cache.php')) {
$routes = include __DIR__ . '/routes_cache.php';
} else {
// генерировать маршруты (например, из аннотаций)
}
// далее стандартный разбор
Результат: После кеширования загрузка маршрутов из файла выполняется быстрее, чем динамическое построение на каждом запросе.