- BrainTools - https://www.braintools.ru -
Каждый PHP-разработчик хотя бы раз задавался вопросом: «А не написать ли свой роутер?» Обычно ответ — «не надо, возьми готовый». И это правильный совет. FastRoute, Symfony Routing, Laravel Router — все они проверены временем и боем.
Но у меня была другая цель. Я хотел проверить гипотезу: можно ли с помощью современных ИИ-инструментов создать production-ready библиотеку, которая не стыдно выложить на Packagist, за один вечер?
Не прототип. Не «MVP, который потом допилим». А полноценную библиотеку с:
Строгой типизацией (PHP 8.4, strict_types)
PHPStan level 9
Полным покрытием тестами
CI/CD пайплайном
Документацией
Публикацией на Packagist
Спойлер: получилось.
ascetic-soft/waypoint [1] — легковесный 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 [2] — IDE на базе VS Code с глубокой интеграцией ИИ. Cursor позволяет вести диалог с ИИ прямо в контексте кодовой базы: он видит файлы проекта, понимает структуру, может читать и редактировать код.
Весь проект был создан 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 минут. Это включая перерывы, обдумывание архитектуры и ревью сгенерированного кода.
Я начал с описания того, что хочу получить. Примерно так:
«Мне нужен 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
Нет ли лишних зависимостей
Логична ли декомпозиция
Самая интересная часть — алгоритм матчинга маршрутов. Вместо простого перебора всех регулярок (как в большинстве роутеров), 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())
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 для извлечения метаданных.
Вот тут ИИ показал себя во всей красе. После формулировки «Напиши полный набор тестов для всех компонентов» я получил 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.
GitHub Actions pipeline с тремя параллельными job’ами:
jobs:
code-style: # PHP CS Fixer (dry-run)
static-analysis: # PHPStan level 9
tests: # PHPUnit + coverage → Codecov
README получился обширный, с примерами для каждой фичи, таблицами параметров, диаграммой архитектуры, бейджами CI, покрытия и версий.
Последний штрих — обратная маршрутизация. Именованные маршруты можно использовать для генерации 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
Роутер — это много однотипного кода: методы get(), post(), put(), delete() отличаются одним параметром. ИИ генерирует такой код мгновенно и безошибочно.
Все @param, @return, @throws, generic-типы вроде list<array{type: 'static'|'param', value: string}> — ИИ выдаёт их правильно и полно. Это критично для PHPStan level 9.
Написание тестов — рутина, от которой большинство разработчиков отлынивают. ИИ генерирует тесты с удовольствием и покрывает сценарии, о которых можно забыть.
composer.json, phpunit.xml.dist, phpstan.neon.dist, .php-cs-fixer.dist.php, Makefile, .github/workflows/ci.yml — всё это ИИ создал правильно с первого-второго раза.
Документация получилась подробная, с примерами кода для каждой фичи, таблицами параметров, ASCII-диаграммой архитектуры.
ИИ предлагает решения, но окончательный выбор — за разработчиком. Например, решение разделить маршруты на trie-совместимые и линейный fallback — это архитектурное решение, которое нужно было осознанно принять.
ИИ может пропустить нетривиальные граничные случаи. Например, что происходит, когда regex параметра может матчить / (кросс-сегментный захват)? Или когда сегмент содержит смесь статического текста и параметра (prefix-{name}.txt)? Эти случаи нужно было явно продумать и указать ИИ.
Иногда ИИ генерирует «рабочий, но некрасивый» код. Нужно не стесняться просить рефакторинг: «Сделай это через readonly-свойства», «Используй named arguments», «Разбей на более мелкие методы».
При генерации большого объёма кода ИИ может забыть о решениях, принятых ранее. Важно следить за единообразием именования, обработки ошибок, порядка параметров.
Не все маршруты помещаются в trie. Паттерн /files/{path:.+} (где regex матчит /) или /report-{year}.pdf (смесь статики и параметра в одном сегменте) — не совместимы с посегментным поиском. Waypoint автоматически определяет это через RouteTrie::isCompatible() и отправляет такие маршруты в линейный fallback.
Trie строится только при первом запросе match(). Индекс имён для URL-генерации — только при первом вызове generate(). Это значит, что если вы загружаете 500 маршрутов, но обрабатываете только один запрос — вы не платите за построение всех индексов.
Маршруты компилируются в обычный PHP-массив:
// cache/routes.php — загружается мгновенно через OPcache
return [
['pattern' => '/users/{id:d+}', 'methods' => ['GET'], 'handler' => [...], ...],
// ...
];
Никакого serialize()/unserialize(), никакого JSON. Просто include + OPcache = нулевые накладные расходы.
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 не прощает небрежности.
Чёткое видение — ИИ отлично выполняет задачи, но плохо ставит их сам себе
Архитектурное мышление [4] — декомпозиция, выбор паттернов, trade-offs
Code review — ИИ может ошибаться, и его код нужно проверять с тем же пристрастием, что и код коллеги
Знание предметной области — PSR-стандарты, особенности PHP 8.4, best practices
Библиотеки с чёткой спецификацией (как роутер)
Проекты, где много шаблонного кода
Задачи с хорошо определёнными интерфейсами (PSR)
Написание тестов и документации
Уникальная бизнес-логика
Код, зависящий от специфического контекста
Оптимизации производительности (нужны бенчмарки, а не интуиция [5] ИИ)
Безопасность (всегда проверяйте вручную)
composer require ascetic-soft/waypoint
Код на GitHub: ascetic-soft/Waypoint [6]
Вопросы, критика, идеи — welcome в комментариях. Если вы тоже создавали проекты с помощью ИИ — делитесь опытом [7], инте��есно сравнить подходы.
Автор: kotafey
Источник [8]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/25686
URLs in this post:
[1] ascetic-soft/waypoint: https://packagist.org/packages/ascetic-soft/waypoint
[2] Cursor: https://cursor.com/
[3] Image: https://sourcecraft.dev/
[4] мышление: http://www.braintools.ru/thinking
[5] интуиция: http://www.braintools.ru/article/6929
[6] ascetic-soft/Waypoint: https://github.com/ascetic-soft/Waypoint
[7] опытом: http://www.braintools.ru/article/6952
[8] Источник: https://habr.com/ru/articles/996728/?utm_source=habrahabr&utm_medium=rss&utm_campaign=996728
Нажмите здесь для печати.