Scalar и OpenAPI: документация API — мастерская и 8 практических задач
Восемь практических задач API: каталог, JSON, ошибки, роли, версии и файлы. Настоящий Scalar, живые PHP-запросы, исходники и запуск Windows/Linux.

Scalar и OpenAPI на практике: документация API, восемь задач, двадцать вариантов настоящих запросов к PHP, исходники и запуск Windows/Linux.
Scalar и OpenAPI: документация, с которой можно работать
Разработчик подключает форму к чужому API и начинает собирать ответы на вопросы: какой адрес вызвать, что передать в JSON, где нужен токен, почему пришёл 422 и какие поля читать в результате. Если ответы разбросаны по переписке и старым примерам, даже простой экран превращается в исследование. Scalar помогает представить контракт API в удобном интерфейсе: с разделами, параметрами, схемами, примерами кода и выполнением запроса.
В этой статье мы соберём собственную документацию сервисной компании и настоящий небольшой PHP API. На странице можно менять параметры, получать каталог заявок, рассчитывать позиции, видеть ошибки формы, проверять учебную роль, разбирать конфликт версии и передавать текстовый файл. Ответы действительно приходят от PHP того же сайта. Это не запись и не заранее нарисованная смена числа в карточке.
Под материалом находится мини-мастерская с восемью задачами и двадцатью вариантами запросов. Тот же API открывается в настоящем Scalar API Reference. Исходники позволяют запустить всё на Windows или Linux, изменить контракт и обработчик, выполнить тесты и разместить документацию рядом со своим приложением. В конце разберём практику Linux/VPS и границы учебной песочницы.
Где это пригодится: команда, интеграция и поддержка
Первый сценарий — работа frontend и backend в одной команде. Экран таблицы требует фильтр, пагинацию, идентификаторы и несколько состояний ошибки. Общий документ помогает обсуждать эти детали до того, как интерфейс наполнится случайными проверками. Разработчик видит, какие значения статуса допустимы, где расположен total и чем пустой результат отличается от отсутствующего ресурса.
Второй сценарий — внешняя интеграция. Партнёру нужен конкретный способ создать заявку, приложить позиции и понять отказ. Удобная документация сокращает число уточнений, особенно если рядом можно отправить безопасный тестовый запрос. Но sandbox должен быть отдельной средой с явными правилами: пример создания заказа не должен неожиданно выставлять реальный счёт или отправлять письмо клиенту.
Третий сценарий — поддержка собственного продукта. Когда возникает жалоба «форма не сохраняется», инженер может повторить запрос с учебными данными, сверить поля и увидеть ответ. Документация не заменяет журналирование и мониторинг, но даёт одинаковую точку входа для разработчика, тестировщика и специалиста сопровождения. Важно описывать не только счастливый путь, но и ожидаемые отказы.
Три части: контракт, интерфейс документации и сервер
OpenAPI — машиночитаемое описание HTTP API. В нём перечисляются операции, параметры, тела запросов, ответы, схемы данных и способы авторизации. Scalar API Reference читает этот документ и строит интерфейс. PHP в нашем проекте принимает запрос и формирует результат. Это три самостоятельные части, поэтому изменение одной не гарантирует автоматического изменения остальных.
Если записать в схеме minimum:1, PHP не начнёт сам запрещать ноль. Если скрыть кнопку проверки в Scalar, endpoint не станет закрытым. Если показать пример ответа 200, это не доказывает, что реальный сервер вернёт ту же структуру. Именно поэтому в архиве есть тесты самого документа и отдельные HTTP-проверки обработчиков.
Мы используем OpenAPI 3.1.1 как зафиксированную версию формата, а не называем её последней доступной редакцией. Полное описание находится в официальной спецификации. Для начала достаточно понимать основные узлы: info, servers, paths и components. Остальные детали удобнее добавлять по конкретной потребности API.
{
"openapi": "3.1.1",
"info": {"title": "API заявок", "version": "1.0.0"},
"servers": [{"url": "https://api.example.com"}],
"paths": {},
"components": {"schemas": {}}
}
Scalar, Swagger UI и обычный README
README удобен для начала работы: установка, переменные окружения, локальная команда и описание архитектуры. Но вручную поддерживать в нём десятки параметров и вариантов ответа трудно. Машиночитаемый контракт позволяет использовать одну структуру для документации и проверок. Сам интерфейс можно выбрать по требованиям команды: Scalar, Swagger UI или другой инструмент, который понимает ваш формат.
В этом материале выбран Scalar API Reference: он показывает схемы, примеры запросов и встроенный клиент. Мы используем self-hosted open-source библиотеку, а не платный сервис публикации документации. Это важное разделение: наличие удобного интерфейса в нашем архиве не означает, что все облачные функции Scalar доступны без отдельной настройки или условий.
При выборе проверьте свой контракт, нужные схемы авторизации, мобильную навигацию, длину описаний и примеры сложных ответов. Красивый первый экран мало говорит о том, насколько легко найти конкретный параметр. Репозиторий и лицензия библиотеки доступны в официальном проекте Scalar. В скачиваемом комплекте сохранены лицензии используемых компонентов.
Что получится в нашей песочнице
Предметная область — заявки небольшой сервисной компании. В каталоге восемнадцать вымышленных записей с тремя статусами. Есть карточка по идентификатору, расчёт нескольких услуг в целых копейках, проверка вложенного клиента, учебные роли, проверка версии и чтение небольшого текстового тела. Это знакомые задачи, которые можно перенести в CRM, личный кабинет или административный интерфейс.
Песочница не хранит изменяемое состояние. После POST не появляется настоящая заявка, после PATCH не меняется общий каталог, а загруженный текст не записывается на диск. Ответы прямо содержат persisted:false или saved:false. Это позволяет нескольким посетителям одновременно пробовать страницу, не менять чужие результаты и не оставлять реальные данные в демонстрационной базе.
Для каждого запроса показываются метод, адрес, тело, статус, выбранные заголовки и время выполнения. Рядом есть объяснение, что этот результат означает для пользовательского интерфейса. Время относится только к текущему запросу и не является сравнением производительности PHP, Scalar или разных серверов.
Мини-мастерская: восемь задач и настоящий PHP
Выберите задачу и вариант запроса. В первом примере можно поменять status, page и limit, затем нажать «Отправить запрос». Сравните массив data и блок meta. Во второй задаче попробуйте существующую карточку и отсутствующий идентификатор. Затем перейдите к JSON: исправьте поля в ошибочном примере и отправьте его снова. Мастерская не подставляет ожидаемый статус вместо ответа сервера.
Кнопка «Вернуть пример» восстанавливает исходные значения. Ответ можно скачать вместе с отправленным запросом и выбранными заголовками, чтобы сохранить результат своего опыта. При смене задачи активный запрос отменяется; устаревший ответ не должен подменить новый экран. Для медленного соединения есть явная отмена и ограничение ожидания.
Ниже располагается кнопка открытия Scalar прямо на странице. Библиотека загружается только по нажатию, чтобы знакомство с короткими примерами не требовало сразу загружать весь интерфейс документации. Отдельная ссылка открывает тот же контракт во всю вкладку. Пользуйтесь только вымышленными данными и публичными токенами из примера: реальные пароли и рабочие API-ключи сюда не нужны.
Установка и локальная сборка без обязательного CDN
В проекте зафиксированы Scalar API Reference 1.78.1 и Bootstrap 5.3.9. Сам API работает на PHP 8.2 или новее и не требует Composer. Node.js 24 LTS используется для сборки и тестов. После npm ci скрипт build копирует браузерную сборку Scalar, локальные стили, контракт и читаемые исходники в public. Готовый архив уже содержит этот каталог.
Таким образом, читатель может сначала запустить PHP и открыть мастерскую, а зависимости Node установить позже, когда захочет изменять сборку. Каталог node_modules не включён в ZIP: он восстанавливается из package-lock.json. Для другой операционной системы нужно выполнить чистую установку, а не переносить готовые Windows-зависимости на Linux.
Версии фиксируют проверенное состояние примера, но не освобождают от обновлений. При переходе на новую версию сначала соберите проект отдельно, выполните тесты и проверьте реальные запросы в интерфейсе. Нельзя делать вывод о совместимости только по тому, что первая страница документации открылась без ошибки.
npm install --save-exact @scalar/api-reference@1.78.1 bootstrap@5.3.9
# Для скачанного проекта с lock-файлом:
npm ci --ignore-scripts
npm run build
npm test
Подключение Scalar к своему OpenAPI
Минимальное подключение состоит из контейнера, браузерной библиотеки и вызова createApiReference. В нашем проекте документ сначала загружается с того же сайта, а servers получает фактический адрес соседнего API. Это позволяет запускать один комплект локально и в каталоге сайта без ручного изменения домена в каждом запросе.
Мы отключаем загрузку стандартных web-шрифтов, сохранение авторизации, встроенный AI Agent и служебные инструменты интерфейса. Не подключаем analytics plugin и не задаём внешний proxy. Для запроса используем customFetch с проверкой точных адресов учебных endpoint. Это настройка конкретной песочницы, а не требование для любого проекта Scalar.
При встраивании также ограничивайте область своих CSS-правил. Общий селектор main или button может изменить внутреннюю разметку документации и окно проверки запроса. В мастерской авторские правила исключают Scalar; мобильную ширину проверяют отдельно для обычной формы, документации и её диалога.
На своём сайте выбирайте конфигурацию под реальную модель доступа. Например, приложение с cookie-сессией потребует отдельного решения для credentials и CSRF; пример с публичными учебными токенами этого не показывает. Параметры и поддерживаемые методы приведены в официальной конфигурации Scalar.
Scalar.createApiReference('#scalar', {
content: openapiDocument,
withDefaultFonts: false,
persistAuth: false,
telemetry: false,
agent: { disabled: true },
showDeveloperTools: 'never',
customFetch: boundedFetch
});
Пример 1. Каталог: фильтр, страница и пустой результат
Каталог — типичная основа интерфейса: таблица заявок, список клиентов, журнал операций или поиск товаров. Клиент отправляет фильтр и размер страницы, сервер возвращает нужный фрагмент и сведения о полном наборе. В нашей фикстуре шесть заявок каждого статуса, поэтому вторая страница по четыре элемента содержит только две записи.
Поменяйте status на in_progress и page на 2. Затем запросите страницу 9. Пустой data при статусе 200 означает, что запрос корректен, но на этой странице ничего нет. В интерфейсе это повод показать пустое состояние и возможность вернуться назад, а не красную ошибку соединения.
Неверный status или слишком большой limit дают 422. Ограничение размера страницы защищает сервер от случайного запроса огромного набора и делает интерфейс предсказуемее. Для большого изменяемого каталога стоит отдельно рассмотреть cursor pagination; наш page/limit выбран для короткого воспроизводимого опыта, а не как универсальная схема для любого объёма.
GET /api/catalog.php?status=in_progress&page=2&limit=4
{
"demo": true,
"data": [
{"id":14,"title":"Проверка щита · объект 14","status":"in_progress","amountMinor":925000,"currency":"RUB","version":3},
{"id":17,"title":"Диагностика линии · объект 17","status":"in_progress","amountMinor":962500,"currency":"RUB","version":3}
],
"meta": {"page": 2, "limit": 4, "total": 6, "pages": 2}
}
Query и path: разные способы передать идентификатор
Параметры поиска обычно удобно помещать в query: status=new, page=2, limit=4. Идентификатор конкретного ресурса часто располагается в пути, например /tickets/3. OpenAPI различает эти места через поле in. Для path-параметра требуется required:true, а имя в описании должно соответствовать переменной в адресе.
В нашей скачиваемой песочнице карточка вызывается как detail.php?id=3. Такой адрес работает на обычном PHP-хостинге без rewrite и предположений о PATH_INFO. Это осознанная переносимая форма учебного endpoint. Она не мешает описать те же данные и ответы в контракте и сравнить их с обычным router API.
Если переносите пример в Hono, Symfony или собственный маршрутизатор, замените путь и обработку идентификатора вместе. Не оставляйте в документации /tickets/{id}, когда сервер фактически принимает только query-параметр: кнопка проверки тогда будет создавать неправильный запрос. Подробные примеры параметров есть в руководстве Swagger.
# Пример для API с router, не адрес текущей песочницы:
/tickets/{id}:
get:
parameters:
- name: id
in: path
required: true
schema: {type: integer, minimum: 1}
Пример 2. Карточка: 200, 404 и понятное состояние экрана
Откройте карточку № 3, затем замените id на 999. Во втором случае PHP возвращает 404 с кодом TICKET_NOT_FOUND и понятным сообщением. Запрос состоялся, сервер ответил, но подходящей записи нет. В frontend это отличается от ситуации, когда сеть недоступна или запрос отменён пользователем.
Для личного кабинета полезно заранее договориться, как показывать такой результат: отдельная страница, сообщение внутри панели или предложение вернуться к списку. Не стоит превращать любой неуспешный статус в одинаковое «что-то пошло не так». Пользователь может открыть старую ссылку после удаления записи, а разработчик должен понимать причину по структуре ответа.
Отдельно проверяйте неверный формат идентификатора. Строка 1e2 не должна случайно превратиться в другую заявку, если контракт ожидает обычное целое число. В песочнице такие значения дают 422. Разница между неверным запросом и отсутствующим ресурсом помогает и интерфейсу, и тестам.
{
"demo": true,
"error": {
"code": "TICKET_NOT_FOUND",
"message": "Учебная заявка не найдена",
"fields": {}
}
}
Пример 3. Вложенный JSON и расчёт нескольких позиций
В форме заявки клиент передаётся объектом customer, а услуги — массивом items. Каждая позиция содержит название, количество и цену в копейках. Такая структура ближе к настоящей форме, чем один текстовый параметр: появляются вложенные поля, ограничения массива и необходимость связать ответ с конкретными строками интерфейса.
PHP проверяет типы и допустимые диапазоны, затем считает итог. В исходном примере две единицы диагностики по 125000 копеек и материалы за 40000 дают 290000 копеек. Числа остаются целыми; форматирование рублей выполняется уже на стороне интерфейса. Для более сложных денег и валют пригодится отдельная библиотека и согласованная политика округления.
Ответ имеет 201 и persisted:false. Он иллюстрирует контракт создания результата расчёта, но не сохраняет заявку в базе. В рабочем приложении создание записи, транзакция, идентификатор, идемпотентность и возможные внешние действия должны быть реализованы отдельно. Читатель не должен путать успешный учебный расчёт с готовой системой заказов.
{
"customer": {"name":"Марина","email":"marina@example.test"},
"items": [
{"title":"Диагностика","quantity":2,"priceMinor":125000},
{"title":"Материалы","quantity":1,"priceMinor":40000}
]
}
Components и $ref: одна схема для нескольких операций
Клиент, позиция и ошибка используются в нескольких местах. Если каждый раз копировать их описание целиком, поля постепенно разойдутся: где-то email станет обязательным, где-то исчезнет ограничение количества, где-то останется старое имя. Общие components/schemas и ссылки $ref уменьшают такое дублирование.
В проекте есть схемы Customer, Item, QuoteInput, Ticket и Error. QuoteInput ссылается на клиента и массив позиций. Scalar показывает эту структуру в документации, а тест проверяет, что ссылки разрешаются. В небольшом проекте контракт хранится одним файлом, поэтому для просмотра не нужны внешние схемы и дополнительные сетевые запросы.
Переиспользование не означает, что любые похожие объекты нужно насильно объединять. Объект создания может отличаться от ответа сервера: пользователь не должен задавать внутренний идентификатор, служебную версию или дату обработки. Разделяйте входные и выходные схемы там, где у них разный смысл. Это помогает обнаружить нежелательные поля ещё на этапе обсуждения API.
{
"QuoteInput": {
"type": "object",
"required": ["customer", "items"],
"additionalProperties": false,
"properties": {
"customer": {"$ref": "#/components/schemas/Customer"},
"items": {
"type": "array", "minItems": 1, "maxItems": 5,
"items": {"$ref": "#/components/schemas/Item"}
}
}
}
}
Пример 4. Ошибки 400, 422 и 415
В четвёртой задаче есть три разных отказа. Повреждённый JSON даёт 400: сервер не смог разобрать документ. Корректный JSON с неверными полями даёт 422: структура прочитана, но данные не подходят. Текстовое тело с Content-Type:text/plain отправляется в JSON endpoint и получает 415: формат запроса не поддерживается.
Попробуйте исправить короткое имя, ошибочную почту, нулевое количество и цену, переданную строкой. Ошибки возвращаются с путями customer.email и items.0.quantity. В интерфейсе их можно сопоставить конкретным полям, сохранить введённый текст и показать общее сообщение над формой. Перезагрузка всей страницы ради одной ошибки здесь не нужна.
HTTP-статус даёт общую категорию результата, а машиночитаемый code помогает программной обработке. Текст message нужен человеку; не стоит строить логику приложения на точном совпадении русской фразы. Перевод или редактура сообщения тогда сломают интеграцию. Дополнительные примеры проверки данных в PHP есть в материале о Symfony Validator.
{
"error": {
"code": "VALIDATION_FAILED",
"message": "Исправьте отмеченные поля",
"fields": {
"customer.email": "Нужен корректный email",
"items.0.quantity": "Целое число от 1 до 20"
}
}
}
Схема не выполняет проверку за ваш PHP
В OpenAPI можно указать тип, обязательность, минимум, максимум и допустимые значения. Эти сведения полезны интерфейсу документации, генераторам и тестам. Но наш PHP всё равно отдельно проверяет вход: метод, Content-Type, размер, форму объекта и конкретные поля. Нельзя считать клиентскую подсказку надёжной границей для сервера.
Например, пользователь может отправить quantity:1.5 напрямую через curl, хотя поле интерфейса выглядит как целочисленное. Или добавить неизвестное свойство, отправить массив вместо объекта и передать status[] вместо строки. Именно такие ситуации включены в тесты песочницы. Они показывают, почему проверка должна работать независимо от Scalar.
В рабочем проекте можно использовать валидатор, связанный с контрактом, либо поддерживать прикладные правила отдельно. В обоих случаях нужны тесты соответствия. Ограничение «не более пяти позиций» и правило «эта услуга недоступна данному клиенту» относятся к разным уровням: второе требует бизнес-контекста, которого одна JSON-схема может не содержать.
$raw = file_get_contents('php://input', false, null, 0, 12289);
if ($raw === false || strlen($raw) > 12288) {
// Вернуть 413 и остановить обработку.
}
$value = json_decode($raw, true, 16, JSON_THROW_ON_ERROR);
// Затем проверить объект, типы и правила конкретной операции.
Пример 5. Bearer: сначала личность, затем право
Запрос без токена получает 401. Запрос с demo-reader и action=edit получает 403: учебная роль известна, но действие ей недоступно. demo-editor допускается к этому действию и получает 200. Так можно объяснить разницу между отсутствием подходящей аутентификации и недостаточными правами без настоящих пользователей и секретов.
В реальном API токен проверяется сервером: срок, подпись или запись в хранилище, область действия, отзыв и доступ к конкретному ресурсу. Сравнение с двумя публичными строками в нашем обработчике выполняет только учебную задачу. Добавление securitySchemes в OpenAPI описывает способ передачи данных, но само по себе не создаёт безопасную авторизацию.
Не сохраняйте рабочие токены в примерах документации, общих коллекциях или скриншотах. В нашей мастерской разрешён выбор только двух демонстрационных строк. В Scalar для этих же операций можно ввести такой публичный токен вручную. Сохранение авторизации между перезагрузками отключено, запросы не отправляют cookie-сессию WordPress.
curl -i 'http://127.0.0.1:8513/api/account.php?action=edit' -H 'Authorization: Bearer demo-reader'
# Ответ: 403. Для учебного 200 замените на demo-editor.
Пример 6. PATCH и конфликт одновременного изменения
Два сотрудника открыли карточку с версией 3. Первый изменил её, а второй продолжил редактирование старого состояния. Если сервер просто перезапишет данные, изменения первого сотрудника могут пропасть. Проверка expectedVersion позволяет обнаружить такой конфликт и попросить клиента перечитать актуальную карточку.
В песочнице исходная версия всегда равна 3. Запрос с expectedVersion:2 возвращает 409 и currentVersion:3. Запрос с версией 3 показывает успешный результат с версией 4, но ничего не сохраняет. Два одинаковых успешных вызова будут воспроизводимы; это проверка контракта, а не настоящая конкурентная работа с общей базой.
Для production одного сравнения в PHP перед UPDATE недостаточно: между чтением и записью другой процесс тоже может изменить строку. Условие версии должно участвовать в атомарной операции, а количество изменённых строк — проверяться. В интерфейсе конфликт не следует автоматически скрывать повторным сохранением: пользователь должен понять, чьи данные изменились и что нужно сопоставить.
UPDATE tickets
SET title = :title, version = version + 1
WHERE id = :id AND version = :expected_version;
-- Проверить число изменённых строк.
-- 0 требует отдельного разбора: ресурс отсутствует или версия изменилась.
Пример 7. Текстовый файл: размер, кодировка и результат
В седьмой задаче можно отправить небольшой UTF-8 текст или выбрать учебный TXT на компьютере. Выбор файла сначала читает его в браузере и помещает текст в поле. Передача на сервер происходит только после отдельного нажатия. PHP считает байты, строки и SHA-256, затем возвращает результат без записи файла на диск.
Этот опыт использует тело text/plain, а не multipart/form-data. Он полезен для понимания прямой передачи текста, предварительной проверки импорта и различия размера в байтах и длины в символах. Русская буква занимает несколько байт UTF-8, поэтому 4096 символов не обязательно поместятся в лимит 4096 байт.
Вариант с большим телом возвращает 413. Пустой или повреждённый UTF-8 текст даёт понятную ошибку. Для полноценной загрузки документов понадобятся дополнительные правила: тип содержимого, размер, имена, безопасное хранение, антивирусная проверка по задаче и контроль доступа. Здесь мы намеренно ограничиваемся маленьким телом в памяти, чтобы показать именно HTTP-контракт.
curl -i -X POST http://127.0.0.1:8513/api/upload.php -H 'Content-Type: text/plain' --data-binary @sample.txt
# В ответе: bytes, lines, sha256, saved:false.
Пример 8. 429, Retry-After и устаревшие операции
Восьмая задача объединяет два важных сообщения клиенту. Endpoint limited.php всегда возвращает учебный 429 и Retry-After:3. Это позволяет посмотреть статус и заголовок без попыток действительно перегрузить сервер. Реального счётчика запросов в песочнице нет, поэтому ожидание трёх секунд не превратит следующий вызов в успешный.
Для рабочего API нужно реализовать ограничитель отдельно и определить, кого считать клиентом, где хранить бюджет и как несколько процессов используют общее состояние. Интерфейс может показать задержку и отключить повторную кнопку на короткое время. Автоматические повторы должны иметь предел; бесконечный мгновенный retry только усиливает нагрузку.
Вторая операция помечена deprecated в OpenAPI и продолжает возвращать 200. Это демонстрирует смысл эволюции контракта: документация сообщает о переходе на новый адрес, но не выключает код. Нужны срок миграции, связь с потребителями и проверка реального использования. Удаление старого endpoint без такого плана может сломать работающую интеграцию.
responses:
'429':
description: Учебный лимит
headers:
Retry-After:
schema: {type: integer, const: 3}
# У другой операции:
deprecated: true
Примеры запросов: показывать разные исходы
Один удачный JSON полезен для первого знакомства, но недостаточен для разработки интерфейса. Добавьте примеры с отсутствующим ресурсом, ошибкой поля, пустым списком, конфликтом и ограничением. В нашем OpenAPI у расчёта есть корректное и ошибочное тело, а у изменения версии — актуальный и устаревший вариант.
Пример должен объяснять смысл значения. Цена 125000 в поле priceMinor требует пояснения, что это копейки, а версия 3 — что это число для проверки конкуренции. Без таких деталей разработчик может правильно отправить JSON и неверно интерпретировать результат. Имена полей и единицы измерения заслуживают не меньше внимания, чем красивые заголовки разделов.
Не помещайте в examples настоящих клиентов или фрагменты production-ответов с персональными данными. Лучше создать небольшой согласованный набор, который проходит реальные проверки. В архиве один такой набор используется и мастерской, и тестами, поэтому пример не живёт отдельно от исполняемого кода.
CORS и proxy: почему наш запрос остаётся на своём сайте
Браузер учитывает origin: схему, хост и порт. Поэтому страница с одного адреса не всегда может свободно прочитать ответ другого API. В нашей мастерской документ, библиотека и PHP endpoint находятся на одном origin. Дополнительный внешний proxy для такого запроса не нужен, и введённые данные не отправляются через сторонний сервис.
Если документация размещена отдельно от API, настройте CORS на стороне API под нужные origin, методы и заголовки. Сервер должен обрабатывать соответствующие запросы, включая preflight там, где он возникает. Добавление произвольного заголовка в frontend не снимает ограничение браузера. Описание механизма есть в руководстве MDN.
Proxy меняет путь передачи данных и требует отдельного решения о доверии. Не вводите реальный токен в документацию, если не понимаете, через какой сервис пойдёт запрос. Наш customFetch допускает только восемь фиксированных учебных путей и запрещает перенаправления. Это ограничение песочницы помогает сохранить понятный маршрут данных.
const url = new URL(request.url);
if (url.origin !== location.origin || !allowedPaths.has(url.pathname)) {
throw new Error('Разрешён только API этой мастерской');
}
return fetch(new Request(request, {
credentials: 'omit',
redirect: 'error'
}));
Windows: запуск и запросы из PowerShell
Установите PHP и Node.js поддерживаемой версии, распакуйте проект в отдельную папку и откройте PowerShell в её корне. Проверьте php –version и node –version. Для изменения сборки выполните npm.cmd ci, build и test; для просмотра уже собранного public достаточно запустить PHP-сервер и открыть index.html по локальному HTTP-адресу.
Не открывайте HTML двойным щелчком через file://. PHP при этом не выполняется, а браузерные запросы работают в другой среде. Статический http.server тоже не заменяет PHP: он способен отдать файл, но не выполнить обработчик. На локальном компьютере используем loopback 127.0.0.1, чтобы не публиковать учебный сервер в сеть.
В PowerShell команда curl могла быть alias другой программы в старых версиях оболочки. Для воспроизводимости пишите curl.exe. JSON с кавычками удобнее хранить в UTF-8 файле без BOM и передавать через –data-binary. Это избавляет от различий экранирования между Bash, PowerShell и командной строкой Windows.
php --version
node --version
npm.cmd ci --ignore-scripts
npm.cmd run build
npm.cmd test
php -S 127.0.0.1:8513 -t public
# В другом окне PowerShell:
curl.exe -i -X POST http://127.0.0.1:8513/api/quote.php -H "Content-Type: application/json" --data-binary "@examples/quote.json"
Linux: тот же проект и чистая установка
На Linux используются те же исходники и package-lock.json. Не переносите node_modules с Windows: выполните npm ci в чистой папке. Для PHP-части этого проекта не нужны база данных и сторонние расширения. Тестовый процесс сам поднимает временный PHP-сервер на свободном loopback-порту, проверяет HTTP-ответы и завершает только свой процесс.
Это удобно для CI: проверка не зависит от заранее занятого порта и от оставшегося сервера предыдущего запуска. При необходимости PHP_BIN позволяет указать конкретный исполняемый файл PHP. Если команда php отсутствует, сначала установите CLI-пакет своей системы и проверьте версию; не подменяйте проверку PHP статическим просмотром JSON.
Локальный запуск на Linux полезен и перед размещением на VPS, но не доказывает работу внешнего домена, TLS и PHP-FPM. Эти уровни проверяются отдельно. Страница может хорошо работать на php -S и не работать в vhost из-за document root, неверного socket или отсутствующей передачи Authorization.
php --version
node --version
npm ci --ignore-scripts
npm run build
npm test
php -S 127.0.0.1:8513 -t public
curl -i 'http://127.0.0.1:8513/api/catalog.php?status=new&page=1&limit=4'
curl -i -X POST http://127.0.0.1:8513/api/quote.php -H 'Content-Type: application/json' --data-binary @examples/quote.json
Linux/VPS: document root, PHP-FPM и HTTPS
Для публичного размещения выберите отдельный домен или согласованный каталог приложения. Document root должен указывать на public, а не на весь исходный проект. Node.js нужен на этапе сборки; отдавать страницу и выполнять PHP может Nginx с PHP-FPM. В архиве есть пример vhost и отдельная инструкция с проверками перед reload.
Замените домен docs.example.com и фактический socket PHP-FPM. Версия socket зависит от установленного пакета, поэтому сначала посмотрите /run/php и состояние сервиса. Путь из примера не является универсальным значением. После nginx -t и reload проверьте JSON endpoint, а также прямое обращение к _core.php: оно должно вернуть 404, не исходный PHP-код.
Встроенный PHP-сервер предназначен для разработки; это прямо описано в PHP Manual. Для внешнего сервера настройте домен, HTTPS и доступные порты обычным способом своего хостинга. У нас также есть отдельные материалы про Nginx и Caddy.
server {
listen 80;
server_name docs.example.com;
root /var/www/scalar-demo/public;
index index.html;
client_max_body_size 16k;
location / { try_files $uri $uri/ =404; }
# Полный allowlist PHP endpoint — в deploy/nginx.conf.
}
# Перед применением:
# sudo nginx -t
# sudo systemctl reload nginx
Проверка контракта и реального поведения
Наш набор тестов решает несколько разных задач. Swagger Parser проверяет документ OpenAPI и разрешение локальных ссылок. Затем Node запускает настоящий PHP и отправляет все двадцать вариантов запросов. Дополнительные проверки сверяют пагинацию, расчёт суммы, отсутствие общего состояния, ограничения размеров, кодировку, методы и обращение к общему служебному файлу.
Такой набор не следует называть исчерпывающей сертификацией API. Он проверяет конкретные свойства нашего небольшого стенда. Полная система может потребовать проверки каждой схемы ответа, нагрузки, прав на ресурсы, транзакций, cookie-сессий и внешних зависимостей. Даже валидный OpenAPI не гарантирует, что обработчик ему соответствует во всех ветках.
В репозитории контракт генерируется из tools/openapi.mjs. После изменения выполните build, чтобы обновить public/openapi.json, затем test. Тест сравнивает построенный документ с исходной структурой и обнаруживает забытое обновление. Проверки этого проекта запускались на Windows и в изолированном Linux-окружении; внешняя конфигурация вашего VPS в этот результат не входит.
npm run build
npm test
# Тесты:
# OpenAPI 3.1 + refs
# 20 вариантов HTTP
# пагинация, деньги, version preview
# method/origin/body limits/UTF-8/Retry-After
Как развивать контракт без неожиданностей для клиентов
Начните с небольшой законченной группы операций. Согласуйте названия полей, обязательность, единицы измерения и формат ошибок. Добавление нового обязательного поля в запрос может сломать существующего клиента, даже если сервер и документация обновились одновременно. Подумайте, какая версия интерфейса ещё работает у пользователя и как долго она будет отправлять старый формат.
Для изменений храните OpenAPI вместе с кодом, рассматривайте diff на ревью и добавляйте примеры миграции. Устаревшая операция должна иметь понятную замену и срок перехода. Полезно проверять использование endpoint по журналам и договариваться с потребителями API заранее. Одна пометка deprecated не уведомит автоматически всех интеграторов.
Не изменяйте ответ незаметно только ради удобства новой страницы документации. Если сумма раньше передавалась в копейках, а теперь стала рублями, старое имя без новой версии создаёт особенно опасную неоднозначность. Контракт — это договорённость о поведении, а не просто красивое описание типов.
Типичные проблемы: что проверять по порядку
Если Scalar открылся, но Test Request не работает, посмотрите фактический URL запроса. Частая ошибка — неверный servers, лишний префикс или адрес localhost в опубликованном документе. Затем проверьте метод, Content-Type и способ передачи авторизации. Успешное отображение схем не подтверждает доступность endpoint.
Если вместо JSON приходит HTML, это может быть страница ошибки Nginx, редирект на вход или обработчик WordPress. Посмотрите статус и заголовки, прежде чем разбирать тело как JSON. В мастерской такой ответ будет обозначен как неподходящий формат, а не превращён в выдуманный успешный результат. Если PHP отдаётся как текст, размещение нужно остановить и исправить конфигурацию исполнения.
Если ошибка возникает только в браузере, а curl работает, проверьте origin, CORS и смешанный HTTP/HTTPS. Если проблема появилась после обновления контракта, сравните опубликованный файл с локальным и очистите нужный кеш. Не меняйте одновременно schema, сервер, proxy и клиент: последовательная проверка одного слоя делает причину понятнее.
Исходники и следующий практический шаг
В ZIP находятся PHP endpoint, интерфейс мастерской, настоящий Scalar, OpenAPI, генератор контракта, тесты, пример тела запроса, инструкции Windows/Linux и конфигурация Nginx. Авторский код доступен по MIT, лицензии сторонних библиотек приложены отдельно. Никаких рабочих токенов, базы клиентов и секретов в комплекте нет.
Для своего проекта выберите одну полезную операцию: каталог, сохранение формы, расчёт или импорт. Опишите успешный ответ и хотя бы два ожидаемых отказа, затем подключите документ к Scalar. После этого проверьте настоящие запросы и поведение интерфейса. Такой порядок превращает документацию в рабочий инструмент команды, а не в страницу, которую однажды красиво оформили и забыли обновлять.
Официальные материалы для продолжения: конфигурация Scalar, репозиторий и лицензии, OpenAPI 3.1.1 и локальный PHP-сервер. Для HTTP-клиента на Node.js пригодится наша мастерская Undici, а для проверки форм — TanStack Form.


