Современный API Reference в Symfony через Scalar
Работая с PHP, испытываешь постоянную необходимость поддерживать документацию в актуальном состоянии. В последнее время PHP-проекты — это API-first, когда бек на PHP, а фронт собирается отдельно.
Тут у нас два пути:
- Spec-first — сначала готовится спецификация всех возможных маршрутов с входными/выходными параметрами, ошибками и пр. Часто это YAML-файл OpenAPI.
- OpenAPI-файл генерируется на основе атрибутов и аннотаций в коде.
В первом случае мы делаем то, что прописано в спецификации, и имеем возможность сравнить, верно ли у нас всё сделано. Во втором случае у нас всегда актуальная схема (после автоматического обновления).
Стандартом в Symfony считается NelmioApiDocBundle. Вплоть до 4 версии в качестве UI всё ещё отдаёт Swagger UI или Redoc. Если поискать, какие ещё есть варианты UI, находишь свежий и современный Scalar (в NelmioApiDocBundle 5+ версии заявлена поддержка) — open-source-рендерер API Reference, но NelmioApiDocBundle генерирует OpenAPI из атрибутов — это эталонный представитель второго типа.
Осмотр конкурента Laravel показывает, что там есть больше различных вариантов (Scribe, Scramble и официальная интеграция со Scalar).
Хотелось бы лёгкий, современный UI для отрисовки готового OpenAPI yaml-файла для первого типа работы с документацией API. И, кажется, Scalar для этого отлично подходит, но вот интеграции из коробки у Symfony со Scalar нет. И чтобы подключить Scalar в Symfony, приходится копировать HTML-страницу с <script> - тегом из доков Scalar и настраивать конфигурацию руками.
В самом Scalar официальный список интеграций покрывает 30+ фреймворков (Express, FastAPI, NestJS, Spring Boot, Laravel), но нет Symfony. На Packagist не было ни одного пакета, который подключал Scalar в Symfony.
В одном своём проекте у меня подход OpenAPI-first, то есть сначала спецификация, потом реализация. Мощный NelmioApiDocBundle тянуть в проект не хотелось. Выбор пал на Scalar. Но одно плохо — нет интеграции. Можно было просто интегрировать своими силами в проекте, но понял, что можно оформить это в виде бандла и в дальнейшем переиспользовать в других проектах. За один вечер я написал первую версию Symfony-бандла, тесты и CI-матрицы. Протестировал это на своём проекте, после чего опубликовал.
Что такое Scalar
Scalar — open-source API-платформа для работы с OpenAPI-документами.
Две части:
- API Reference — интерактивный рендерер OpenAPI 3.x; загружает спеку на клиенте, со встроенным API-клиентом (возможность делать тестовые запросы), есть сниппеты кода на разных языках (curl, PHP, Python, Go), разные темы оформления и разные схемы авторизации;
- API client — десктопный клиент уровня Postman, работает офлайн и читает те же OpenAPI-файлы.
На данный момент в GitHub: около 16 тыс. звёзд, создан в 2023 году, насчитывает более 100 релизов, живой проект. Есть интеграция для Laravel (scalar/laravel, официальная, в org scalar) — это основа, на что смотреть при реализации своего бандла. Что там есть? «Тонкий пакет», который отдаёт одну страницу со Scalar, указывая на любой OpenAPI-документ.
Бандл: что он делает
alex-frolov/scalar-symfony рендерит Scalar API Reference в Symfony из любого OpenAPI-документа. Один маршрут, нет привязки к тому, каким образом сгенерировалась спецификация. Может быть статичный openapi.yaml, может swagger-php, NelmioApiDocBundle или API Platform.
Для установки и настройки необходимо отредактировать два файла.
Подключаем бандл:
Редактируем настройки:
Всё, reference открывается по адресу /scalar. Страница — небольшой HTML с CDN-скриптом и Scalar.createApiReference('#scalar-api-reference', {...}), где конфиг сериализуется XSS-безопасно (JSON_HEX_TAG/APOS/AMP/QUOT).
Настоящий запрос, настоящий 201
Лучше всего показать это на живом примере, а не мокапом. Бандл обслуживает API Reference платформы Tender Platform (Symfony 8.1, highload-аукционный API; спека — OpenAPI 3.1). Через «Test Request» открываем эндпоинт POST /auth/register «Регистрация компании (создаётся компания pending + первый пользователь admin)» — заполнил JSON-тело, нажал Send и получил:
Реальный ответ с данными. При тестировании много вариантов, больше вариативность, чем у Swagger-UI, работает как интерактивный клиент.
Контроль качества
Итоговое состояние:
- Имеются 16 функциональных тестов с 46 проверками;
- Проводиться статический анализ через PHPStan level max;
- Контроль за стилем кода посредством PHP-CS-Fixer, правила @Symfony;
- CI проверки PHP 8.2/8.3/8.5 с Symfony 6.4/7.2/7.4/8.0, включая опции --prefer-lowest (5 job), no-dev smoke-тест и composer validate --strict;
- Ужесточение политик безопасности в конфиге, добавлены валидация на этапе компиляции, так режим attribute без Symfony Security роняет cache:clear с внятной ошибкой вместо HTTP 500 на первом запросе, пустой cdn, путь без ведущего /, пустой attribute — теперь всё блокируется на уровне конфига;
- Добавлена документация по безопасности, включая SRI (SHA-384 для указанного файла CDN), рекомендации по CSP/nonce, рецепт self-hosting.
Два найденных артефакта:
- PHPUnit 11.5 помечает тесты как risky, когда ErrorHandler Symfony идёт после boot ядра, фикс — восстановление exception-handler в tearDown().
- Контейнер кэшируется по классу ядра и окружению, из-за этого функциональные тесты с разными конфигами бандла должны использовать уникальный cache-dir на каждый конфиг, иначе тесты запускаются из устаревшего контейнера с битым состоянием.
CI и проблемы
После пуша в репозиторий GitHub запустился CI. Половина задач из GitHub Actions начала падать со случайной ошибкой:
Your github oauth token for github.com contains invalid characters
Как выяснилось, причина была в setup-php: он пишет Actions-токен GITHUB_TOKEN с префиксом ghs_ в глобальный auth.json composer, а Composer версии 2.8 принимает только ghp_/gho_/github_pat_. Падение тасков было плавающим, зависело от того, заставил ли rate-limit реально использовать токен или нет, поэтому часть тасков проходила. Даже composer config --unset падал с той же ошибкой. Пришлось делать фикс: удалять auth.json на виртуальном раннере перед проверкой composer validate --no-check-publish, а токен оставлять для установки зависимостей, где он защищает от rate-limit.
Roadmap и предложение
Я открыл предложение в организации Scalar:
Discussion #9920 — «Proposal: official Symfony integration (scalar/symfony)»
Суть: принять бандл в org scalar как scalar/symfony, повторив ровно путь scalar/laravel — перенос или форк, добавить трекинг _integration: symfony, занести Symfony в официальные интеграции. Бандл опубликован, протестирован, работает в проде, усилия на принятие почти нулевые, а разработчики Symfony получают то, что у Laravel уже есть.
Если вы Symfony-разработчик и хотите этого, то ставьте реакции и комментируйте обсуждение. Внимание мейнтейнеров следует за сигналом сообщества.
Как начать использовать?
- composer require alex-frolov/scalar-symfony
- Укажите scalar_symfony.url на любой OpenAPI-документ (или сгенерируйте через NelmioApiDocBundle / swagger-php)
- Импортируйте маршруты, откройте /scalar
В README описано более подробно.
Итог
У Scalar не было официальной интеграции с Symfony, была потребность, был сделан бандл.
- Бандл v0.1.0, alex-frolov/scalar-symfony.
- Поддерживаемые версии PHP >= 8.2, Symfony 6.4 / 7.2+ / 8.x.
- Использовано на платформе Tender Platform.
Александр Фролов · frolov.guru · @frolov_aleksander