Организация маршрута аккаунта в PHP-приложении

Раздел: Маршрутизация в веб-приложениях -> Маршруты PHP

Основные подходы к маршрутизации аккаунта

Базовый фронт-контроллер с обработкой через switch

Наиболее эффективное решение для небольших проектов - единая точка входа index.php, которая анализирует параметр route из URL и подключает соответствующий контроллер. Пример структуры файла:


<?php
// index.php - единая точка входа
$route = $_GET['route'] ?? 'home';

switch ($route) {
    case 'account':
        require 'controllers/account.php';
        break;
    case 'login':
        require 'controllers/login.php';
        break;
    default:
        http_response_code(404);
        require 'views/404.php';
        break;
}
?>

Этот подход прост, быстр и легко поддерживается. Каждый кейс загружает отдельный файл с логикой. Для аккаунта можно выделить контроллер account.php, который отвечает за профиль, настройки и т.д.

Типичные ошибки и их решение
  • Ошибка 404 при неверном маршруте - всегда добавлять ветку default с корректным ответом.
  • Проблемы с безопасностью - не использовать $_GET['route'] напрямую в include без валидации. Лучше проверять по белому списку или применять switch, как в примере.
  • Дублирование кода - вынести общие проверки (авторизация, права доступа) в единый bootstrap-файл.

Альтернативные решения для разных сценариев

Как организовать маршрутизацию, поддерживающую вложенные пути (например, /account/profile)?

Разбор URL с помощью explode и условий

Вместо простого switch можно разбить URL на сегменты и обрабатывать их:


<?php
$path = trim($_SERVER['REQUEST_URI'], '/');
$segments = explode('/', $path);

if ($segments[0] === 'account') {
    $action = $segments[1] ?? 'index';
    require "controllers/account/{$action}.php";
}
?>

Этот вариант подходит для RESTful-подобных ссылок. Возможная проблема - уязвимость к path traversal (использование ../). Решение - проверять имя файла через регулярное выражение или белый список.

Как реализовать гибкую маршрутизацию с параметрами (например, /account/123)?

Использование регулярных выражений в роутере

Регулярные выражения позволяют извлекать параметры из URL:


<?php
$routes = [
    'account'   => '/^\/account(\/(\d+))?$/',
    'profile'   => '/^\/account\/profile$/',
];

$uri = $_SERVER['REQUEST_URI'];
foreach ($routes as $name => $pattern) {
    if (preg_match($pattern, $uri, $matches)) {
        // $matches содержит найденные параметры
        require "controllers/{$name}.php";
        break;
    }
}
?>

Такой роутер сложнее в написании, но даёт полный контроль. Частая ошибка - некорректные регулярки, приводящие к false-negative. Рекомендуется тестировать каждое правило.

Как сделать маршрутизацию, похожую на MVC-фреймворки, с классом Router?

Объектно-ориентированный роутер с методами add() и dispatch()

Создаётся класс Router, который хранит ассоциативный массив маршрутов и вызывает замыкания или контроллеры:


<?php
class Router {
    private array $routes = [];

    public function add(string $method, string $path, callable $handler): void {
        $this->routes[] = compact('method', 'path', 'handler');
    }

    public function dispatch(string $method, string $uri): void {
        foreach ($this->routes as $route) {
            if ($route['method'] === $method && preg_match('#^' . $route['path'] . '$#', $uri)) {
                call_user_func($route['handler']);
                return;
            }
        }
        http_response_code(404);
        echo 'Not Found';
    }
}

$router = new Router();
$router->add('GET', '/account', function() {
    echo 'Страница аккаунта';
});
$router->add('GET', '/account/\d+', function() {
    echo 'Профиль пользователя';
});

$router->dispatch($_SERVER['REQUEST_METHOD'], parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
?>

Этот вариант удобен для средних проектов. Проблема - сложность отладки при большом количестве маршрутов. Рекомендуется подключать роутер через автозагрузку.

Общие проблемы и как их избежать
  • Инъекция кода при использовании require с динамическим путём - всегда проверять имя файла по белому списку или использовать метод, не включающий прямое выполнение (например, вызывать методы классов).
  • Потеря контекста при передаче параметров - при использовании замыканий убедиться, что они имеют доступ к глобальным переменным через use или передавать их как аргументы.
  • Конфликт маршрутов - располагать более конкретные правила выше общих.

Подробные примеры реализации с кодом и результатами

Ниже приведены расширенные примеры для каждого из рассмотренных подходов с демонстрацией работы.

Пример 1. Простейший switch с контроллером account.php

Файловая структура:

Пример

project/
  index.php
  controllers/
    account.php
    login.php
  views/
    404.php

Код index.php:

Пример

<?php
$route = $_GET['route'] ?? 'home';

switch ($route) {
    case 'account':
        // Предполагается, что account.php получает данные из БД
        require 'controllers/account.php';
        break;
    case 'login':
        require 'controllers/login.php';
        break;
    default:
        http_response_code(404);
        require 'views/404.php';
}
?>

Пример содержимого controllers/account.php:

Пример

<?php
session_start();
if (!isset($_SESSION['user_id'])) {
    header('Location: /?route=login');
    exit;
}
$user = ['name' => 'Иван', 'email' => 'ivan@example.com'];
?>
<h1>Аккаунт пользователя <?= htmlspecialchars($user['name']) ?></h1>
<p>Email: <?= htmlspecialchars($user['email']) ?></p>

Результат при переходе на /?route=account (если пользователь авторизован):

Аккаунт пользователя Иван

Email: ivan@example.com

При отсутствии сессии происходит редирект на логин.

Пример 2. Разбор сегментов URL для вложенных страниц аккаунта

Предположим, что URL имеет вид /account/profile или /account/settings. Код index.php:

Пример

<?php
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$uri = trim($uri, '/');
$segments = explode('/', $uri);

// Первый сегмент - модуль
$module = $segments[0] ?? 'home';

if ($module === 'account') {
    $action = $segments[1] ?? 'index';
    $actionFile = __DIR__ . '/controllers/account/' . basename($action) . '.php';
    
    if (file_exists($actionFile)) {
        require $actionFile;
    } else {
        http_response_code(404);
        echo 'Страница не найдена';
    }
} else {
    // другие модули
    http_response_code(404);
    echo 'Модуль не найден';
}
?>

Здесь использован basename() для предотвращения path traversal. Пример вызова controllers/account/profile.php:

Пример

<?php
// profile.php – страница профиля
?>
<h2>Профиль пользователя</h2>
<p>Здесь отображается информация профиля.</p>

Результат при переходе на /account/profile:

Профиль пользователя

Здесь отображается информация профиля.

Пример 3. Роутер с регулярными выражениями и извлечением параметров

Рассмотрим сценарий, когда нужно передавать ID пользователя: /account/42. Код index.php (с использованием роутера как функции):

Пример

<?php
function dispatch($uri) {
    $routes = [
        '^/account$' => function() {
            echo 'Список аккаунтов';
        },
        '^/account/(\d+)$' => function($id) {
            echo 'Аккаунт #' . htmlspecialchars($id);
        },
    ];

    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);
    echo '404 - Не найдено';
}

dispatch(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
?>

Пример вызова /account/42:

Аккаунт #42

А если маршрут /account/abc (не цифры) - сработает правило ^/account$? Нет, будет 404, так как паттерн не совпадёт. Это правильное поведение.

Пример 4. Класс Router с поддержкой HTTP-методов

Полноценный роутер, аналогичный используемым в фреймворках. Класс Router.php:

Пример

<?php
class Router {
    private array $routes = [];

    public function add(string $method, string $path, callable $handler): void {
        $this->routes[] = [
            'method' => strtoupper($method),
            'pattern' => '#^' . $path . '$#',
            'handler' => $handler,
        ];
    }

    public function run(): void {
        $method = $_SERVER['REQUEST_METHOD'];
        $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
        
        foreach ($this->routes as $route) {
            if ($route['method'] === $method && preg_match($route['pattern'], $uri, $matches)) {
                array_shift($matches);
                call_user_func_array($route['handler'], $matches);
                return;
            }
        }
        http_response_code(405);
        echo '405 Method Not Allowed или 404 Not Found';
    }
}
?>

Использование в index.php:

Пример

<?php
require 'Router.php';

$router = new Router();

$router->add('GET', '/account', function() {
    echo 'Список аккаунтов (GET)';
});
$router->add('POST', '/account', function() {
    echo 'Создание нового аккаунта (POST)';
});
$router->add('GET', '/account/(\d+)', function($id) {
    echo 'Просмотр аккаунта #' . htmlspecialchars($id);
});

$router->run();
?>

Результат при GET-запросе на /account/7:

Просмотр аккаунта #7

При POST-запросе на /account:

Создание нового аккаунта (POST)

Этот подход легко расширяется для поддержки middleware или групп маршрутов.

Маршрут аккаунта PHP - comments

En
Index php route account (php)