Skip to content
mxApi
Единая точка входа публичного API для MODX Revolution 2 и 3 — маршруты под своим префиксом, bearer-токены, scope поверх прав MODX, каталог эндпоинтов и OpenAPI из живого реестра.
  1. Компоненты
  2. mxApi
  3. Настройка и расширение
  4. Контракт и совместимость

Контракт и совместимость

Провайдер — сторонний код: он наследует AbstractEndpoint, реализует EndpointInterface, зовёт Request, Response, Config. Значит, у него есть право знать, на что можно опираться, а что завтра перепишут.

Эта страница отвечает на три вопроса: что обещано, как это проверяется и что будет с обеими линиями пакета дальше.

Что обещано

Каждый класс ядра (MxApi\Core\*) объявляет свою природу тегом в docblock.

ТегЗначение
@apiОбещание стороннему коду. Публичные и protected-сигнатуры не меняются внутри старшей версии
@internalВнутренняя механика. Может измениться в любом релизе без предупреждения

Публичная часть (@api)

Класс / интерфейсЗачем нужен провайдеру
Endpoint\AbstractEndpointбаза эндпоинта: readParams(), readPaging(), readCursor(), nextCursor()
Endpoint\ProcessorEndpointэндпоинт поверх процессора MODX и его хуки
Endpoint\EndpointInterfaceконтракт эндпоинта: getMetadata(), handle()
Endpoint\EndpointMetadata, Endpoint\ParameterMetadataпаспорт эндпоинта и его параметров, включая константы
Endpoint\EtagAwareInterfaceнеобязательный интерфейс: версия ответа до его сборки
Endpoint\EndpointContextплатформа, конфигурация, авторизация внутри handle()
Http\Request, Http\Response, Http\ApiExceptionвход, ответ и ошибки
Configчтение настроек пакета
Provider\ProviderInterfaceконтракт провайдера
Middleware\MiddlewareInterfaceконтракт промежуточного обработчика
Auth\AuthContext, Platform\PlatformUser, Platform\ProcessorResultчто приходит в эндпоинт при авторизации и от процессора
Platform\PlatformInterfaceчитать можно, реализовывать не нужно: адаптеры платформы поставляет сам пакет

protected-методы AbstractEndpoint и ProcessorEndpoint — тоже часть контракта: ради них класс и наследуют.

Внутренняя часть (@internal)

Kernel, Routing\Router, Routing\RouteMatch, Registry\EndpointRegistry, Registry\CatalogFilter, OpenApi\OpenApiGenerator, Http\ETag, Paging\Cursor, Auth\TokenService и репозитории, Maintenance\MaintenanceService, встроенные обработчики Middleware\RateLimitMiddleware и Middleware\IdempotencyMiddleware.

Пользоваться ими из провайдера не надо: то, что вам от них нужно, доступно через публичные классы. Например, курсор создаётся не Cursor::encode(), а nextCursor() — иначе вы завяжетесь на формат подписи, который вправе измениться.

Публичный HTTP-контракт — отдельная вещь

Маршруты, конверт success / data / meta, коды ошибок, имена параметров и таблицы БД — это контракт с вызывающей системой, а не с кодом провайдера. Он тоже соблюдается по semver, и его правила описаны там, где описан он сам: Токены и аутентификация, Каталог и OpenAPI.

Как это проверяется

Граница не живёт «в головах» — она проверяется тестами пакета, и их два.

Слепок контракта. Сигнатуры и константы всех @api-классов записаны в фикстуру. Любая правка публичной сигнатуры роняет тест, и diff фикстуры прямо показывает, что именно меняется у интеграторов. Класс ядра, забывший объявить @api или @internal, тест тоже роняет — «внутренний по умолчанию» здесь не работает.

Паритет ядра. Каталог src/Core в линиях для MODX 2 и MODX 3 побайтово одинаков: различаются только адаптеры платформы. Манифест хэшей лежит в обеих линиях одним и тем же файлом, поэтому забытый перенос правки роняет тесты той линии, куда её не донесли — а не всплывает у интегратора, который написал провайдера один раз на обе версии MODX.

Версии и линии

У пакета две линии, они развиваются параллельно и отличаются только платформой:

ЛинияMODXPHP
1.xRevolution 2.6+7.4+
2.xRevolution 3.0+8.1+

Старшая цифра здесь не значит «новее»: 2.x — это не следующая версия после 1.x, а та же функциональность на другой платформе. Возможности появляются в обеих линиях одновременно и с одинаковой средней цифрой: 1.1.0 и 2.1.0 — один и тот же релиз.

Правила смены цифр внутри линии обычные:

ЦифраКогда меняется
старшаяломающая правка @api-класса или публичного HTTP-контракта
средняяновые возможности, совместимые со старым кодом провайдеров
младшаяисправления

Что считается ломающим: удаление или переименование публичного метода, новый обязательный параметр, новый метод в интерфейсе, который реализуют сторонние классы, смена смысла возвращаемого значения. Что ломающим не считается: новый необязательный ключ паспорта эндпоинта, новый необязательный интерфейс (как EtagAwareInterface), новый метод AbstractEndpoint, изменение чего угодно с тегом @internal.

Что будет с линией для MODX 2

Линия 1.x живёт, пока её можно развивать без ломающей правки ядра. Когда очередная возможность потребует сломать публичный контракт в двойке, поддержка линии на этом остановится: версии 2.0.0 для MODX 2 не будет.

Причина простая. Ломающая правка означает, что установленные провайдеры чинить придётся всем, а на MODX 2 «починить» обычно значит «перейти на MODX 3». Растить в двойке параллельную несовместимую ветку — это два разных ядра под одним именем и конец паритету, ради которого провайдер и пишется один раз.

Что это значит на практике: код провайдера, написанный по @api-контракту сегодня, работает в обеих линиях и продолжит работать в двойке до конца её поддержки. Готовиться к переезду нужно не к дате, а к событию — объявлению о заморозке линии; оно будет в changelog пакета.

Что это значит для вашего пакета

  • Опирайтесь только на @api. Всё нужное там есть; если кажется, что нет — вероятно, задача решается иначе, чем через внутренний класс.
  • Читайте changelog при обновлении mxApi. Ломающие правки описываются там поимённо: какая сигнатура изменилась и что делать.
  • Пишите провайдера один раз на обе линии. Платформенно-зависимый код — это getModx() и то, что вокруг него; всё остальное переносится без правок. ProcessorEndpoint переносится целиком, кроме адресации самого процессора (Свой эндпоинт).
  • Свои эндпоинты версионируйте сами. Ваш path и scope — это ваш публичный контракт с интегратором, и mxApi за него не отвечает. Для смены несовместимого маршрута есть route_aliases: старый путь остаётся рабочим и указывает на тот же эндпоинт.

Дальше