В предыдущей статье я разбирал OpenAPI как часть архитектуры API. После этого остаётся механическая проблема: route, validation, DTO, Resource, enum и исключения уже описаны в коде, но разработчик повторяет их в спецификации.
Именно такую работу мне всё меньше хочется выполнять вручную. Не потому, что AI умеет сочинять документацию, а потому, что coding agent умеет пройти по проекту и сопоставить уже существующие части контракта.
Не написать документацию, а собрать контракт
Запрос «сделай Swagger» слишком широкий. Он подталкивает модель заполнить пробелы правдоподобным текстом, а в API правдоподобная выдумка опаснее честного пропуска.
Рабочая постановка выглядит иначе:
repository
↓
route and middleware
↓
request DTO and validation
↓
application operation
↓
response DTO or Resource
↓
exceptions and error mapping
↓
OpenAPI diffЭто не творческое письмо. Это исследование связей и перенос фактов из одного формального представления в другое.
Правила, которые я задаю один раз
Для проекта я фиксирую короткий порядок:
1. Найди route и middleware.
2. Определи controller или action.
3. Найди FormRequest, MapRequestPayload или request DTO.
4. Извлеки validation rules и enum.
5. Проследи вызов до response DTO или Resource.
6. Найди exception handler / subscriber.
7. Используй существующие reusable schemas.
8. Не придумывай поля и ошибки.
9. Не меняй публичный контракт без отдельного решения.
10. Сгенерируй OpenAPI и проверь результат.Такой список нужен не одному endpoint. Он превращает обновление документации в повторяемый workflow.
Сначала research, потом изменения
Я не разрешаю агенту сразу расставлять Attributes. Первый проход — только чтение:
Исследуй endpoint создания заказа. Не изменяй код. Определи route, авторизацию, входной DTO, validation rules, вызываемый сервис, формат ответа и возможные исключения. Сравни это с OpenAPI-описанием и приложи ссылки на файлы.
Хороший отчёт короткий и проверяемый:
POST /api/orders
Controller: StoreOrderController
Request: StoreOrderRequest
Response: OrderResource
Success: 201
Errors found in code: 401, 409, 422
OpenAPI drift:
- response 409 отсутствует
- у quantity не указано minimum: 1
- поле delivery_date описано без format: dateУже на этом шаге агент полезен как аудитор. Если карта неверна, я исправляю понимание до того, как ошибка размножится по схемам.
План отделён от реализации
После исследования появляется план с конкретными объектами, а не обещание «обновить Swagger»:
1. Добавить CreateOrderRequest schema.
2. Переиспользовать OrderResponse.
3. Добавить ConflictErrorResponse.
4. Обновить POST /api/orders.
5. Сгенерировать openapi.json.
6. Сравнить схему с validation и тестами.Только после проверки этого списка агент меняет файлы. Разделение research, plan и implementation кажется медленнее одного большого запроса, но экономит ревью: спор о контракте происходит до диффа.
Unknown лучше выдумки
Пусть контроллер возвращает $this->json($result), а тип $result не удаётся восстановить ни по сигнатуре, ни по фабрике, ни по тестам. У агента два варианта:
A. «Структуру ответа определить не удалось»
B. «Вероятно, ответ выглядит так...»В документации API подходит только вариант A. Моё правило записано буквально:
Модель легко создаёт убедительный example response. Убедительность не делает его контрактом.
Laravel: цепочка для агента
В Laravel маршрут обычно читается достаточно прямо:
Route
↓
Middleware
↓
FormRequest
↓
Controller
↓
Action
↓
Resource
↓
Exception HandlerИз правила:
'quantity' => [
'required',
'integer',
'min:1',
'max:100',
],можно получить type: integer, minimum: 1 и maximum: 100. Из backed enum — допустимые значения. Из OrderResource — форму успешного ответа, если Resource не зависит от скрытого runtime-контекста.
Современный swagger-php предпочитает PHP Attributes, а L5-Swagger использует его генератор и Swagger UI. Для coding agent это проще старого DocBlock-синтаксиса: атрибуты имеют структурированные аргументы и проверяются PHP.
Symfony уже автоматизирует часть сам
В Symfony карта похожа:
Route
↓
Controller
↓
MapRequestPayload / DTO
↓
Symfony Validator
↓
Handler
↓
Response DTO
↓
ExceptionSubscriberNelmioApiDocBundle уже извлекает request body из MapRequestPayload и использует Symfony Attributes. Поэтому агенту не всегда нужно генерировать всю схему. Полезнее найти то, чего автоматический проход не видит: бизнес-ошибки, permissions, нестандартные headers, idempotency и примеры.
Чем больше способен вывести инструмент, тем меньше текста должен писать агент. Я не плачу за дублирование только потому, что теперь его делает модель.
Bitrix и legacy: сначала фактический контракт
В собственном API на Bitrix автоматизации обычно меньше, поэтому эффект от исследования выше. Если проект уже разделён на Controller, Request, Response, Service и Exception, агент проходит знакомую цепочку и обновляет Attributes для swagger-php.
Legacy интереснее. Допустим, существует обработчик:
if ($_REQUEST['action'] === 'create') {
// 250 строк авторизации, проверок, ORM и JSON
}Просить сразу написать OpenAPI рано. Сначала нужен отчёт о фактическом поведении:
входные параметры и обязательность
приведение типов и проверки
успешный ответ
ветки ошибок
побочные эффекты
неоднозначные местаТакой документ становится картой для последующего разделения обработчика. Если контракт невозможно определить однозначно, это результат исследования, а не неудача агента.
Синхронизация вместо разовой генерации
Самая полезная автоматизация начинается не с новой документации, а с обычной задачи. Например, при добавлении external_id разработчик меняет DTO, validation, service, базу, Resource и тесты — и забывает спецификацию.
Я хочу, чтобы правило проекта срабатывало по самому факту изменения публичного API:
если изменён route, request, response, enum
или публичная ошибка,
найди затронутую OpenAPI operation,
обнови её и проверь генерациюТогда документация входит в Definition of Done, а не зависит от памяти автора задачи.
TASK
↓
RESEARCH
↓
PLAN
↓
IMPLEMENTATION
├── code
├── validation
├── tests
└── OpenAPI
↓
VERIFY
├── tests
├── OpenAPI generation
└── contract diff
↓
REPORTПроверка рассинхронизации
После генерации агент может сравнить код и спецификацию. Здесь не требуется архитектурное чутьё — нужны точные пары:
Validator: quantity max = 100
OpenAPI: quantity maximum = 1000
Controller: 201 Created
OpenAPI: 200 OK
PHP enum: draft, active, archived
OpenAPI: draft, activeКаждая такая разница должна остановить проверку. Автоматически выбрать правильную сторону нельзя: иногда устарела схема, иногда код, иногда оба расходятся с договорённостью команды.
Агент хорошо находит противоречие. Решение о контракте остаётся у разработчика.
OpenAPI как вход для других инструментов
Качественный openapi.json обслуживает не только Swagger UI:
PHP code
↓
OpenAPI
↓
client SDK
frontend types
mocks
contract tests
AI contextПоэтому ошибка в спецификации размножается дальше. Сгенерированный TypeScript type делает выдуманное поле убедительнее, но не реальнее. Чем больше потребителей у схемы, тем строже должно быть правило Unknown > Invented.
Что я делегирую, а что оставляю себе
Агенту можно поручить исследование endpoint, поиск связанных классов, извлечение validation rules и enum, обновление Attributes, переиспользование schemas, генерацию примеров из подтверждённых типов, запуск генератора и поиск рассинхронизации.
Осторожность начинается там, где нужно решить, каким должен быть публичный API: границы endpoint, семантика status codes, бизнес-ошибки, versioning, backward compatibility, permissions и security. Здесь агент собирает факты и варианты, но не получает право молча менять договор между системами.
Ускорение не в наборе Attributes
Пятьдесят строк OpenAPI Attributes агент напишет быстрее меня. Это небольшая экономия.
Сильнее меняется сам цикл: раньше я должен был вспомнить о документации, найти нужную схему, обновить и проверить. Теперь workflow замечает изменение контракта, показывает затронутые операции, обновляет OpenAPI и возвращает diff вместе с тестами.
Автоматизируется не набор текста. Автоматизируется обязанность помнить о повторяющемся действии.
После Swagger тот же вопрос возникает вокруг миграций, changelog, permissions matrix и примеров интеграции: если изменение уже видно в коде, почему человек должен каждый раз вручную вспоминать обо всех его отражениях?
О том, как вместе с этой обязанностью меняется моя роль разработчика, я написал отдельно — в личной заметке.