Маршрутизация категории товара: от простого к продвинутому

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

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

Маршрут для отображения товаров определённой категории является типовой задачей веб-приложений. URL обычно имеет вид /category/{slug} или /products/category/{id}. Ниже рассматриваются несколько способов реализации такого маршрута на чистом PHP, без использования фреймворков, и с использованием современных библиотек.

Основное эффективное решение – FastRoute

Как реализовать маршрут категории товара с поддержкой параметров, производительностью и простотой?

Библиотека FastRoute (nikic/FastRoute) предоставляет быстрый и гибкий роутер. Она подходит для проектов, где не требуется полный фреймворк, но нужна надёжная маршрутизация.


// Установка: composer require nikic/fast-route

// index.php
require 'vendor/autoload.php';

$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
    $r->addRoute('GET', '/category/{slug}', 'Controller@category');
});

$httpMethod = $_SERVER['REQUEST_METHOD'];
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

$routeInfo = $dispatcher->dispatch($httpMethod, $uri);
switch ($routeInfo[0]) {
    case FastRoute\Dispatcher::FOUND:
        $handler = $routeInfo[1];
        $vars = $routeInfo[2];
        // Вызов контроллера с параметрами
        list($class, $method) = explode('@', $handler);
        $controller = new $class;
        echo $controller->$method($vars['slug']);
        break;
    case FastRoute\Dispatcher::NOT_FOUND:
        http_response_code(404);
        echo 'Страница не найдена';
        break;
    case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
        http_response_code(405);
        echo 'Метод не разрешён';
        break;
}

Типичные ошибки:

  • Неверный порядок маршрутов: FastRoute обрабатывает маршруты в порядке добавления, поэтому более специфичные маршруты должны быть перед общими.
  • Забыт parse_url() – без него параметры запроса (query string) могут повлиять на сопоставление.
  • Отсутствие кэширования: для production рекомендуется кэшировать скомпилированные маршруты через FastRoute\cachedDispatcher().

Цель: быстрая разработка с минимальным кодом, поддержка переменных сегментов, кэширование для высокой производительности.

Вариант 1: Ручной разбор URL с explode и switch

Как обработать маршрут категории без внешних зависимостей, используя лишь встроенные функции PHP?


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

if (count($segments) >= 2 && $segments[0] === 'category') {
    $slug = $segments[1];
    // Загрузка товаров категории
    echo "Товары категории: $slug";
} else {
    http_response_code(404);
    echo 'Страница не найдена';
}

Проблемы и ограничения:

  • Невозможность гибко задавать шаблоны (например, /category/{slug}/page/{num} без дополнительных условий).
  • При добавлении новых маршрутов код быстро разрастается и становится трудно поддерживаемым.
  • Нет поддержки методов HTTP (GET, POST и т.д.).

Когда использовать: только для самых простых приложений с одним-двумя маршрутами, где не планируется расширение.

Вариант 2: Использование регулярных выражений (preg_match)

Как реализовать маршрут категории с более сложными условиями, например, с обязательным префиксом и опциональными параметрами?


$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$pattern = '#^/category/(?P<slug>[a-zA-Z0-9\-]+)(/(?P<page>\d+))?$#';

if (preg_match($pattern, $uri, $matches)) {
    $slug = $matches['slug'];
    $page = $matches['page'] ?? 1;
    echo "Категория $slug, страница $page";
} else {
    http_response_code(404);
    echo 'Не найдено';
}

Сложности:

  • Регулярные выражения трудно читать и отлаживать.
  • При большом количестве маршрутов производительность падает из-за перебора всех паттернов.
  • Нет автоматического кэширования – каждый запрос выполняет preg_match.

Когда использовать: если нужно быстрое прототипирование и маршруты не меняются, но требуется гибкость переменных сегментов.

Вариант 3: Роутер из фреймворка (Laravel / Symfony)

Как профессионально организовать маршрут категории в рамках полноценного фреймворка?

Пример для Laravel (файл routes/web.php):


Route::get('/category/{slug}', 'ProductController@category');

Symfony (YAML):


# config/routes.yaml
product_category:
    path: /category/{slug}
    controller: App\Controller\ProductController::category

Возможные проблемы:

  • Привязка к конкретному фреймворку, сложность переноса.
  • Избыточность для небольших проектов.

Когда использовать: при разработке в составе Laravel, Symfony или другого фреймворка – это стандартный подход.

Вариант 4: Использование атрибутов PHP 8 (Symfony)

Как определить маршрут категории непосредственно в методе контроллера, используя современные возможности языка?


use Symfony\Component\Routing\Annotation\Route;

class ProductController
{
    #[Route('/category/{slug}', name: 'product_category')]
    public function category(string $slug): Response
    {
        // ...
    }
}

Недостатки:

  • Требуется конфигурация для сканирования атрибутов (например, в Symfony – Doctrine Annotation Reader).
  • Нестандартный синтаксис может отпугнуть новичков.

Когда использовать: в современных проектах на Symfony 6+ или при использовании атрибутов с собственным роутером (например, с библиотекой PHP-Router).

Расширенные примеры реализации роутинга категории товара

Пример 1: Самописный роутер с поддержкой middleware и проверкой метода

Пример

// Router.php
class Router
{
    private array $routes = [];

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

    public function dispatch(string $method, string $uri): void
    {
        foreach ($this->routes as $route) {
            if ($route['method'] !== $method) continue;
            $pattern = '#^' . preg_replace('/\{([a-zA-Z_]+)\}/', '(?P<$1>[^/]+)', $route['pattern']) . '$#';
            if (preg_match($pattern, $uri, $matches)) {
                $params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
                // Применение middleware (цепочка)
                $next = function() use ($route, $params) {
                    call_user_func($route['handler'], $params);
                };
                foreach (array_reverse($route['middlewares']) as $mw) {
                    $next = fn() => $mw($params, $next);
                }
                $next();
                return;
            }
        }
        http_response_code(404);
        echo "Страница не найдена";
    }
}

// Использование
$router = new Router();
$authMiddleware = function($params, $next) {
    if (!isset($_SESSION['user'])) {
        http_response_code(401);
        echo "Доступ запрещён";
        return;
    }
    $next();
};

$router->add('GET', '/category/{slug}', function($params) {
    echo "Товары категории: " . htmlspecialchars($params['slug']);
}, [$authMiddleware]);

$router->dispatch($_SERVER['REQUEST_METHOD'], parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
При GET /category/electronics? (без авторизации) – вернёт 401.
При GET /category/electronics (с авторизацией) – "Товары категории: electronics"

Пример 2: Использование PSR-7 и PSR-15 для роутинга

Пример

// Требуются библиотеки: psr/http-message, psr/http-server-handler, nyholm/psr7
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Nyholm\Psr7\Response;

class ProductCategoryHandler implements RequestHandlerInterface
{
    public function handle(ServerRequestInterface $request): Response
    {
        $slug = $request->getAttribute('slug');
        $body = "Товары категории: $slug";
        return new Response(200, [], $body);
    }
}

// Пример с FastRoute и PSR-7 (используется middleware-диспетчер)
$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
    $r->addRoute('GET', '/category/{slug}', ProductCategoryHandler::class);
});
// Далее – адаптация PSR-7 запроса к FastRoute, вызов handler

Этот подход позволяет легко комбинировать роутинг с любыми PSR-15 middleware.

Пример 3: Генерация URL по имени маршрута (reverse routing)

Пример

// Допустим, используется библиотека aura/router
$router = new Aura\Router\RouterContainer();
$map = $router->getMap();
$map->get('product.category', '/category/{slug}', ['controller' => 'ProductController', 'action' => 'category']);

// Генерация URL
$generator = $router->getGenerator();
$url = $generator->generate('product.category', ['slug' => 'phones']);
// Результат: /category/phones
Сгенерированный URL: /category/phones

Пример 4: Кэширование маршрутов с FastRoute в production

Пример

$cacheFile = __DIR__ . '/route_cache.php';
if (file_exists($cacheFile)) {
    $dispatcher = FastRoute\cachedDispatcher(function(FastRoute\RouteCollector $r) {
        $r->addRoute('GET', '/category/{slug}', 'handler');
    }, ['cacheFile' => $cacheFile]);
} else {
    $dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
        $r->addRoute('GET', '/category/{slug}', 'handler');
    });
}

При первом запросе создаётся кэш-файл, последующие запросы загружают его, что значительно ускоряет работу.

Пример 5: Маршрут с вложенными параметрами (подкатегории)

Пример

// FastRoute: /category/{slug}/{subslug?}
$r->addRoute('GET', '/category/{slug}[/{subslug}]', 'handler');
// Обработчик получает slug и необязательный subslug

Это позволяет строить иерархические URL, например /category/electronics/phones.

Маршрут категории товара PHP - comments

En
Php route product category (php)