Предыстория: зачем ещё один роутер?
Каждый PHP-разработчик хотя бы раз задавался вопросом: «А не написать ли свой роутер?» Обычно ответ — «не надо, возьми готовый». И это правильный совет. FastRoute, Symfony Routing, Laravel Router — все они проверены временем и боем.
Но у меня была другая цель. Я хотел проверить гипотезу: можно ли с помощью современных ИИ-инструментов создать production-ready библиотеку, которая не стыдно выложить на Packagist, за один вечер?
Не прототип. Не «MVP, который потом допилим». А полноценную библиотеку с:
-
Строгой типизацией (PHP 8.4,
strict_types) -
PHPStan level 9
-
Полным покрытием тестами
-
CI/CD пайплайном
-
Документацией
-
Публикацией на Packagist
Спойлер: получилось.
Что получилось: Waypoint
ascetic-soft/waypoint — легковесный PSR-15 роутер со следующими возможностями:
-
PSR-15 совместимость — реализует
RequestHandlerInterface, работает с любым PSR-7/PSR-15 стеком -
Атрибутная маршрутизация — объявление маршрутов через PHP 8
#[Route]атрибуты прямо на контроллерах -
Быстрый prefix-trie матчинг — статические сегменты за O(1), динамические — только когда нужно
-
Middleware-пайплайн — глобальные и per-route PSR-15 middleware
-
Группы маршрутов — общие префиксы и middleware
-
Кеширование — компиляция маршрутов в PHP-файл для OPcache
-
Автоматический DI — параметры маршрута,
ServerRequestInterface, сервисы из контейнера -
URL-генерация — обратная маршрутизация по именам
-
Диагностика — обнаружение конфликтов, дубликатов, затенённых маршрутов
Масштаб проекта
|
Метрика |
Значение |
|---|---|
|
Строк кода (src/) |
~2 100 |
|
Строк тестов (tests/) |
~2 650 |
|
Файлов в src/ |
18 |
|
Тестовых файлов |
16 |
|
Коммитов |
8 |
|
Время разработки |
~5.5 часов |
|
PHPStan |
Level 9 |
|
Зависимости |
Только PSR-интерфейсы |
Как проходил процесс
Инструмент: Cursor IDE
Я использовал Cursor — IDE на базе VS Code с глубокой интеграцией ИИ. Cursor позволяет вести диалог с ИИ прямо в контексте кодовой базы: он видит файлы проекта, понимает структуру, может читать и редактировать код.
Хронология (по git log)
Весь проект был создан 12 февраля 2026 года. Вот хронология коммитов:
17:16 init project — каркас проекта, composer.json, базовые классы
17:22 phpstan level 9 — настройка статического анализа, фикс типов
17:25 makefile fixer — Makefile для удобной разработки
17:44 tree match — реализация prefix-trie для быстрого матчинга
17:57 docs && ci — README, GitHub Actions CI/CD
18:14 tests — полный набор юнит-тестов
22:44 Url Generator — обратная маршрутизация
22:56 Url Generator base url — поддержка абсолютных URL
Между первым и последним коммитом прошло 5 часов 40 минут. Это включая перерывы, обдумывание архитектуры и ревью сгенерированного кода.
Этап 1: Архитектура и каркас (17:16)
Я начал с описания того, что хочу получить. Примерно так:
«Мне нужен PSR-15 совместимый PHP-роутер. PHP 8.4, strict types. Поддержка атрибутов #[Route] на контроллерах. FastRoute-style плейсхолдеры {name} и {name:regex}. Middleware pipeline. Группы маршрутов. Минимум зависимостей — только PSR-интерфейсы.»
ИИ предложил архитектуру:
Router (PSR-15 RequestHandlerInterface)
├── RouteCollection
│ ├── RouteTrie — prefix-tree для быстрого матчинга
│ └── Route[] — линейный fallback для сложных паттернов
├── AttributeRouteLoader — чтение #[Route] атрибутов через Reflection
├── MiddlewarePipeline — FIFO PSR-15 middleware
├── RouteHandler — вызов контроллера с DI
├── RouteCompiler — компиляция/загрузка кеша
└── RouteDiagnostics — обнаружение конфликтов
Я одобрил структуру, и за несколько итераций диалога были созданы все базовые файлы: composer.json, атрибут #[Route], value-объекты Route и RouteMatchResult, коллекция маршрутов, загрузчик атрибутов.
Что важно: я не просто жал «принять всё». На каждом шаге я проверял:
-
Соответствует ли код PSR-стандартам
-
Правильно ли используются readonly-свойства PHP 8.4
-
Нет ли лишних зависимостей
-
Логична ли декомпозиция
Этап 2: Prefix-Trie — сердце роутера (17:44)
Самая интересная часть — алгоритм матчинга маршрутов. Вместо простого перебора всех регулярок (как в большинстве роутеров), Waypoint использует prefix-trie (префиксное дерево).
Идея:
-
Каждый маршрут разбивается на сегменты по
/ -
Статические сегменты — ключи в hash-map (O(1) lookup)
-
Динамические сегменты (
{id},{slug:w+}) — проверяются regex только когда нужно -
Если паттерн не совместим с trie (например,
prefix-{name}.txt), маршрут попадает в линейный fallback
Вот ключевой метод матчинга:
public function match(
string $method,
array $segments,
int $depth = 0,
array $params = [],
array &$allowedMethods = [],
): ?array {
if ($depth === count($segments)) {
foreach ($this->routes as $route) {
if ($route->allowsMethod($method)) {
return ['route' => $route, 'params' => $params];
}
foreach ($route->getMethods() as $m) {
$allowedMethods[$m] = true;
}
}
return null;
}
$segment = $segments[$depth];
// 1. Статический потомок — O(1) hash-map lookup
if (isset($this->staticChildren[$segment])) {
$result = $this->staticChildren[$segment]->match(
$method, $segments, $depth + 1, $params, $allowedMethods,
);
if ($result !== null) {
return $result;
}
}
// 2. Динамические потомки — в порядке приоритета
foreach ($this->paramChildren as $child) {
if (preg_match($child['regex'], $segment)) {
$childParams = $params;
$childParams[$child['paramName']] = $segment;
$result = $child['node']->match(
$method, $segments, $depth + 1, $childParams, $allowedMethods,
);
if ($result !== null) {
return $result;
}
}
}
return null;
}
ИИ предложил эту структуру, но я попросил доработать несколько моментов:
-
Добавить бэктрекинг (если статическая ветка не нашла — пробуем динамическую)
-
Собирать
allowedMethodsдля корректного 405-ответа -
Проверку совместимости паттерна с trie (
isCompatible())
Этап 3: Атрибутная маршрутизация
PHP 8 атрибуты — мощный инструмент для декларативного описания маршрутов:
#[Route('/api/users', middleware: [AuthMiddleware::class])]
class UserController
{
#[Route('/', methods: ['GET'], name: 'users.list')]
public function list(): ResponseInterface { /* ... */ }
#[Route('/{id:d+}', methods: ['GET'], name: 'users.show')]
public function show(int $id): ResponseInterface { /* ... */ }
#[Route('/{id:d+}', methods: ['PUT'], name: 'users.update')]
public function update(int $id, ServerRequestInterface $request): ResponseInterface { /* ... */ }
}
Атрибут #[Route] работает на двух уровнях:
-
На классе — задаёт префикс пути и общие middleware
-
На методе — определяет конкретный маршрут
Атрибут — IS_REPEATABLE, что позволяет одному методу обслуживать несколько маршрутов. AttributeRouteLoader использует Reflection API для извлечения метаданных.
Этап 4: Тесты (18:14)
Вот тут ИИ показал себя во всей красе. После формулировки «Напиши полный набор тестов для всех компонентов» я получил 2 650 строк тестового кода, покрывающего:
-
RouterTest— интеграционные тесты роутера -
RouteCollectionTest— матчинг маршрутов, приоритеты, 404/405 -
RouteTrieTest— тесты prefix-trie: статические/динамические сегменты, бэктрекинг -
AttributeRouteLoaderTest— загрузка атрибутов, классовые префиксы -
MiddlewarePipelineTest— порядок выполнения middleware, FIFO -
RouteHandlerTest— DI-инъекция параметров, приведение типов -
RouteDiagnosticsTest— обнаружение конфликтов -
RouteCompilerTest— компиляция и загрузка кеша -
UrlGeneratorTest— генерация URL, query-параметры
При этом тесты были не «для галочки» — в них проверялись граничные случаи: trailing slashes, пустые пути, конфликтующие маршруты, nullable-параметры, приведение типов.
Конечно, я проверял тесты и при необходимости просил доработать покрытие для edge cases.
Этап 5: CI/CD и документация (17:57)
GitHub Actions pipeline с тремя параллельными job’ами:
jobs:
code-style: # PHP CS Fixer (dry-run)
static-analysis: # PHPStan level 9
tests: # PHPUnit + coverage → Codecov
README получился обширный, с примерами для каждой фичи, таблицами параметров, диаграммой архитектуры, бейджами CI, покрытия и версий.
Этап 6: URL Generator (22:44–22:56)
Последний штрих — обратная маршрутизация. Именованные маршруты можно использовать для генерации URL:
$router->get('/users/{id:d+}', $handler, name: 'users.show');
$url = $router->generate('users.show', ['id' => 42]);
// => /users/42
$url = $router->generate('users.show', ['id' => 42], absolute: true);
// => https://example.com/users/42
Что ИИ делает хорошо
1. Шаблонный код
Роутер — это много однотипного кода: методы get(), post(), put(), delete() отличаются одним параметром. ИИ генерирует такой код мгновенно и безошибочно.
2. PHPDoc и типизация
Все @param, @return, @throws, generic-типы вроде list<array{type: 'static'|'param', value: string}> — ИИ выдаёт их правильно и полно. Это критично для PHPStan level 9.
3. Тесты
Написание тестов — рутина, от которой большинство разработчиков отлынивают. ИИ генерирует тесты с удовольствием и покрывает сценарии, о которых можно забыть.
4. Конфигурационные файлы
composer.json, phpunit.xml.dist, phpstan.neon.dist, .php-cs-fixer.dist.php, Makefile, .github/workflows/ci.yml — всё это ИИ создал правильно с первого-второго раза.
5. README
Документация получилась подробная, с примерами кода для каждой фичи, таблицами параметров, ASCII-диаграммой архитектуры.
Где ИИ нуждается в контроле
1. Архитектурные решения
ИИ предлагает решения, но окончательный выбор — за разработчиком. Например, решение разделить маршруты на trie-совместимые и линейный fallback — это архитектурное решение, которое нужно было осознанно принять.
2. Edge cases
ИИ может пропустить нетривиальные граничные случаи. Например, что происходит, когда regex параметра может матчить / (кросс-сегментный захват)? Или когда сегмент содержит смесь статического текста и параметра (prefix-{name}.txt)? Эти случаи нужно было явно продумать и указать ИИ.
3. Качество кода
Иногда ИИ генерирует «рабочий, но некрасивый» код. Нужно не стесняться просить рефакторинг: «Сделай это через readonly-свойства», «Используй named arguments», «Разбей на более мелкие методы».
4. Консистентность
При генерации большого объёма кода ИИ может забыть о решениях, принятых ранее. Важно следить за единообразием именования, обработки ошибок, порядка параметров.
Технические решения, которыми горжусь
Двухуровневый матчинг
Не все маршруты помещаются в trie. Паттерн /files/{path:.+} (где regex матчит /) или /report-{year}.pdf (смесь статики и параметра в одном сегменте) — не совместимы с посегментным поиском. Waypoint автоматически определяет это через RouteTrie::isCompatible() и отправляет такие маршруты в линейный fallback.
Lazy-инициализация
Trie строится только при первом запросе match(). Индекс имён для URL-генерации — только при первом вызове generate(). Это значит, что если вы загружаете 500 маршрутов, но обрабатываете только один запрос — вы не платите за построение всех индексов.
OPcache-дружественный кеш
Маршруты компилируются в обычный PHP-массив:
// cache/routes.php — загружается мгновенно через OPcache
return [
['pattern' => '/users/{id:d+}', 'methods' => ['GET'], 'handler' => [...], ...],
// ...
];
Никакого serialize()/unserialize(), никакого JSON. Просто include + OPcache = нулевые накладные расходы.
DI с приведением типов
RouteHandler анализирует сигнатуру метода контроллера и автоматически приводит параметры маршрута к нужному типу:
// Маршрут: /users/{id:d+}
// Параметр из URL: string "42"
public function show(int $id) { /* $id === 42 (int) */ }
Порядок разрешения: ServerRequestInterface → route-параметры → контейнер → default values → nullable.
Статистика и цифры
-
8 коммитов за 5 часов 40 минут
-
18 файлов в
src/, 16 тестовых файлов -
~4 800 строк PHP-кода суммарно
-
0 внешних зависимостей (только PSR-интерфейсы)
-
PHPStan level 9 — максимальный уровень строгости
-
3 параллельных CI job’а: code style, static analysis, tests + coverage
Выводы
ИИ — это мультипликатор, а не замена
ИИ не написал этот проект за меня. Я принимал архитектурные решения, ревьюил каждый файл, указывал на проблемы, просил доработки. Но ИИ колоссально ускорил процесс.
Без ИИ этот проект занял бы у меня 3–5 рабочих дней. С ИИ — один вечер. При этом качество кода не пострадало: PHPStan level 9 не прощает небрежности.
Что нужно от разработчика
-
Чёткое видение — ИИ отлично выполняет задачи, но плохо ставит их сам себе
-
Архитектурное мышление — декомпозиция, выбор паттернов, trade-offs
-
Code review — ИИ может ошибаться, и его код нужно проверять с тем же пристрастием, что и код коллеги
-
Знание предметной области — PSR-стандарты, особенности PHP 8.4, best practices
Когда это работает лучше всего
-
Библиотеки с чёткой спецификацией (как роутер)
-
Проекты, где много шаблонного кода
-
Задачи с хорошо определёнными интерфейсами (PSR)
-
Написание тестов и документации
Когда стоит быть осторожным
-
Уникальная бизнес-логика
-
Код, зависящий от специфического контекста
-
Оптимизации производительности (нужны бенчмарки, а не интуиция ИИ)
-
Безопасность (всегда проверяйте вручную)
Попробовать
composer require ascetic-soft/waypoint
Код на GitHub: ascetic-soft/Waypoint
Вопросы, критика, идеи — welcome в комментариях. Если вы тоже создавали проекты с помощью ИИ — делитесь опытом, инте��есно сравнить подходы.
Автор: kotafey


