Продвинутые возможности

Внедрение зависимостей, генерация URL, кэширование маршрутов и диагностика.

Содержание

Внедрение зависимостей

RouteHandler автоматически разрешает параметры метода контроллера в следующем порядке:

  1. ServerRequestInterface — текущий PSR-7 запрос
  2. Параметры маршрута — сопоставляются по имени с приведением типов (int, float, bool)
  3. Сервисы контейнера — разрешаются из PSR-11 контейнера по type-hint
  4. Значения по умолчанию — используются, если доступны
  5. Nullable-параметры — получают null
#[Route('/orders/{id:\d+}', methods: ['GET'])]
public function show(
    int $id,                          // параметр маршрута (авто-приведение)
    ServerRequestInterface $request,  // текущий запрос
    OrderRepository $repo,            // из контейнера
    ?LoggerInterface $logger = null,  // контейнер или значение по умолчанию
): ResponseInterface {
    // ...
}

Порядок разрешения параметров позволяет смешивать параметры маршрута, объект запроса и сервисы контейнера в любом порядке в сигнатуре метода.


Генерация URL

Генерируйте URL из именованных маршрутов (обратная маршрутизация):

$registrar = new RouteRegistrar();

// Регистрация именованных маршрутов
$registrar->get('/users',          [UserController::class, 'list'], name: 'users.list');
$registrar->get('/users/{id:\d+}', [UserController::class, 'show'], name: 'users.show');

// Создание роутера
$router = new Router($container, $registrar->getRouteCollection());

// Генерация URL через генератор
$url = $router->getUrlGenerator()->generate('users.show', ['id' => 42]);
// => /users/42

$url = $router->getUrlGenerator()->generate('users.list', query: ['page' => 2, 'limit' => 10]);
// => /users?page=2&limit=10

Параметры автоматически URL-кодируются. Лишние параметры игнорируются. Недостающие обязательные параметры вызывают MissingParametersException.

Абсолютные URL

Установите базовый URL (схема + хост) для генерации полных URL:

$router->setBaseUrl('https://example.com');

$url = $router->getUrlGenerator()->generate('users.show', ['id' => 42], absolute: true);
// => https://example.com/users/42

Если absolute: true используется без настроенного базового URL, выбрасывается BaseUrlNotSetException.

Прямое использование UrlGenerator

use AsceticSoft\Waypoint\UrlGenerator;

$generator = new UrlGenerator($router->getRouteCollection(), 'https://example.com');
$url = $generator->generate('users.show', ['id' => 42]);               // относительный
$url = $generator->generate('users.show', ['id' => 42], absolute: true); // абсолютный

Генерация URL работает с кэшированными маршрутами — имена и шаблоны маршрутов сохраняются в файле кэша.


Кэширование маршрутов

Скомпилируйте маршруты в PHP-файл для мгновенной загрузки в продакшене.

Компиляция кэша

use AsceticSoft\Waypoint\Cache\RouteCompiler;

$registrar = new RouteRegistrar();
$registrar->scanDirectory(__DIR__ . '/Controllers', 'App\\Controllers');

$compiler = new RouteCompiler();
$compiler->compile($registrar->getRouteCollection(), __DIR__ . '/cache/routes.php');

Загрузка из кэша

$cacheFile = __DIR__ . '/cache/routes.php';
$compiler  = new RouteCompiler();

$router = new Router($container);

if ($compiler->isFresh($cacheFile)) {
    $router->loadCache($cacheFile);
} else {
    $registrar = new RouteRegistrar();
    $registrar->scanDirectory(__DIR__ . '/Controllers', 'App\\Controllers');
    $compiler->compile($registrar->getRouteCollection(), $cacheFile);

    $router = new Router($container, $registrar->getRouteCollection());
}

Компилятор генерирует самодостаточный PHP-класс с match-выражениями и предвычисленными планами разрешения аргументов. Файл загружается через OPcache с нулевыми накладными расходами, минуя Reflection и парсинг атрибутов.

Не забывайте очищать файл кэша после добавления или изменения маршрутов. Во время разработки пропускайте кэширование.


Диагностика маршрутов

Инспектируйте зарегистрированные маршруты и выявляйте проблемы:

use AsceticSoft\Waypoint\Diagnostic\RouteDiagnostics;

$diagnostics = new RouteDiagnostics($router->getRouteCollection());

// Вывести форматированную таблицу маршрутов
$diagnostics->listRoutes();

// Обнаружить конфликты
$report = $diagnostics->findConflicts();

if ($report->hasIssues()) {
    $diagnostics->printReport();
}

Обнаруживаемые проблемы

Проблема Описание
Дублирующиеся пути Маршруты с одинаковыми шаблонами и пересекающимися HTTP-методами
Дублирующиеся имена Несколько маршрутов с одним и тем же именем
Перекрытые маршруты Более общий шаблон скрывает более конкретный

Запускайте диагностику во время разработки или в CI-конвейере для раннего обнаружения конфликтов.


Обработка исключений

Waypoint выбрасывает специфические исключения при ошибках маршрутизации:

Исключение HTTP-код Когда
RouteNotFoundException 404 Ни один шаблон маршрута не совпал с URI
MethodNotAllowedException 405 URI совпал, но HTTP-метод не разрешён
RouteNameNotFoundException Маршрут с указанным именем не найден
MissingParametersException Не предоставлены обязательные параметры маршрута
BaseUrlNotSetException Запрошен абсолютный URL, но базовый URL не настроен
use AsceticSoft\Waypoint\Exception\RouteNotFoundException;
use AsceticSoft\Waypoint\Exception\MethodNotAllowedException;

try {
    $response = $router->handle($request);
} catch (RouteNotFoundException $e) {
    // Вернуть ответ 404
} catch (MethodNotAllowedException $e) {
    // Вернуть ответ 405 с заголовком Allow
    $allowed = implode(', ', $e->getAllowedMethods());
}

HEAD -> GET fallback: Согласно RFC 7231 §4.3.2, если маршрут для HEAD не зарегистрирован, но существует маршрут GET для того же URI, автоматически используется маршрут GET. Дополнительная настройка не требуется.


Архитектура

Waypoint построен на модульной архитектуре, где каждый компонент имеет единственную ответственность:

RouteRegistrar          — fluent-регистрация маршрутов, загрузка атрибутов, группы
Router                  (PSR-15 RequestHandlerInterface)
├── RouteCollection
│   ├── RouteTrie           — префиксное дерево для быстрого сопоставления
│   └── Route[]             — линейное regex-сопоставление для сложных шаблонов
├── MiddlewarePipeline      — FIFO-выполнение PSR-15 middleware
├── RouteHandler            — вызов контроллера с внедрением зависимостей
├── UrlGenerator            — обратная маршрутизация (имя + параметры → URL)
├── RouteCompiler           — компиляция/загрузка кэша маршрутов
└── RouteDiagnostics        — обнаружение конфликтов и отчётность

RouteRegistrar занимается построением маршрутов (ручная регистрация, загрузка атрибутов, группы) и создаёт RouteCollection. Router — чистый PSR-15 RequestHandlerInterface, ответственный только за сопоставление и диспетчеризацию.

RouteTrie обрабатывает большинство маршрутов с O(1) поиском на сегмент. Маршруты с шаблонами, которые невозможно выразить в дереве (смешанные сегменты вроде prefix-{name}.txt или захваты через несколько сегментов), автоматически используют линейное regex-сопоставление.