Skip to content
  1. Компоненты
  2. MiniShop3
  3. Разработка
  4. API Router

API Router ​

MiniShop3 маршрутизирует API через библиотеку FastRoute. Два API, две схемы авторизации.

Архитектура ​

Два типа API ​

СвойствоManager APIWeb API
Префикс/api/mgr/*/api/v1/*
НазначениеАдминка MODXФронтенд магазина
Точка входаconnector.phpassets/.../api.php
АвторизацияMODX сессии + HTTP_MODAUTHТокены MS3TOKEN
MiddlewareAuthMiddleware, PermissionMiddleware, AnyPermissionMiddlewareServiceCheckMiddleware, TokenMiddleware, CorsMiddleware, RateLimitMiddleware
Файл роутовconfig/routes/manager.phpconfig/routes/web.php

Структура файлов ​

core/components/minishop3/
├── config/
│   ├── routes/
│   │   ├── manager.php                    # Системные роуты Manager API
│   │   └── web.php                        # Системные роуты Web API
│   ├── ms3.routes.d/
│   │   ├── manager/example-addon.php.dist # Пример для аддонов (Manager)
│   │   └── web/example-addon.php.dist     # Пример для аддонов (Web)
│   └── routes_manager.custom.example.php  # Пример файла со своими роутами

core/config/
├── ms3_routes_manager.custom.php          # Свои Manager-роуты, создаёт установка
├── ms3_routes_web.custom.php              # Свои Web-роуты, создаёте сами
└── ms3.routes.d/                          # Модульные роуты аддонов
    ├── manager/                           # Manager API фрагменты
    └── web/                               # Web API фрагменты

Базовое использование ​

Определение роутов ​

php
use MiniShop3\Router\Response;

// Простой GET роут
$router->get('/api/mgr/my-endpoint', function($params) use ($modx) {
    return Response::success(['data' => 'value']);
});

// Middleware можно навесить и флюентно, на возвращённый Route
$router->get('/api/mgr/other', $handler)
    ->middleware(new AuthMiddleware($modx, 'mgr'));

// Middleware принимается и строкой — роутер создаст объект с $modx
$router->get('/api/mgr/third', $handler, [\My\Addon\Middleware\Check::class]);

// Обработчик тоже можно задать строкой 'Класс@метод'
$router->get('/api/mgr/fourth', '\My\Addon\Controller@index');

// POST роут
$router->post('/api/mgr/items', function($params) use ($modx) {
    $data = json_decode(file_get_contents('php://input'), true);
    return Response::success(['created' => true]);
});

// PUT роут
$router->put('/api/mgr/items/{id}', function($params) use ($modx) {
    $id = $params['id'];
    return Response::success(['updated' => $id]);
});

// DELETE роут
$router->delete('/api/mgr/items/{id}', function($params) use ($modx) {
    return Response::success(['deleted' => true]);
});

Параметры URL ​

php
// Один параметр
$router->get('/api/mgr/products/{id}', function($params) use ($modx) {
    $id = $params['id'];  // Значение из URL
    return Response::success(['product_id' => $id]);
});

// Несколько параметров
$router->get('/api/mgr/orders/{order_id}/products/{product_id}', function($params) use ($modx) {
    $orderId = $params['order_id'];
    $productId = $params['product_id'];
    return Response::success([
        'order_id' => $orderId,
        'product_id' => $productId
    ]);
});

Группировка роутов ​

php
use MiniShop3\Router\Middleware\AuthMiddleware;
use MiniShop3\Router\Middleware\PermissionMiddleware;

$router->group('/api/mgr/catalog', function($router) use ($modx) {

    // GET /api/mgr/catalog/products
    $router->get('/products', function($params) use ($modx) {
        return Response::success(['products' => []]);
    });

    // GET /api/mgr/catalog/categories
    $router->get('/categories', function($params) use ($modx) {
        return Response::success(['categories' => []]);
    });

    // POST /api/mgr/catalog/products
    $router->post('/products', function($params) use ($modx) {
        return Response::success(['created' => true]);
    });

}, [
    // Middleware для всей группы
    new AuthMiddleware($modx, 'mgr'),
    new PermissionMiddleware($modx, 'msproduct_save')
]);

Контроллеры ​

Сложную логику выносят в контроллер:

php
$router->group('/api/mgr/orders', function($router) use ($modx) {

    $router->get('', function($params) use ($modx) {
        $controller = new \MiniShop3\Controllers\Api\Manager\OrdersController($modx);
        return $controller->getList($params);
    });

    $router->get('/{id}', function($params) use ($modx) {
        $controller = new \MiniShop3\Controllers\Api\Manager\OrdersController($modx);
        return $controller->get($params);
    });

    $router->put('/{id}', function($params) use ($modx) {
        $body = json_decode(file_get_contents('php://input'), true) ?: [];
        $allParams = array_merge($params, $body);

        $controller = new \MiniShop3\Controllers\Api\Manager\OrdersController($modx);
        return $controller->update($allParams);
    });

});

Response ​

Обработчики роутов возвращают JSON через класс MiniShop3\Router\Response:

php
use MiniShop3\Router\Response;

// Успешный ответ
Response::success($data, $message, $statusCode);

// Ответ с ошибкой
Response::error($message, $statusCode, $errors, $errorCode, $data);

// То же, но машинный код обязателен — так удобнее для Web API
Response::errorWithCode($errorCode, $message, $statusCode, $errors, $data);

// Редирект и ответ из процессора MODX
Response::redirect($url);
Response::fromProcessor($processorResponse);

Четвёртый аргумент error() — тот самый error_code, который ниже обещан в каждом ответе об ошибке; без него роутер подставит код по HTTP-статусу. Пятый, data, несёт контекст, не привязанный к полю: existing_id у конфликта, состояние корзины у отказа по остатку. Заголовки добавляет ->header() на готовом ответе.

Формат ответа ​

Успешный ответ:

json
{
  "success": true,
  "message": "Operation completed",
  "data": {
    "id": 123,
    "name": "Product"
  }
}

Ответ с ошибкой:

json
{
  "success": false,
  "message": "Validation failed",
  "code": 422,
  "errors": {
    "name": "Field is required"
  },
  "error_code": "validation_failed"
}

Ключи code и error_code есть в любом ответе об ошибке, даже когда errors пустой. data добавляется только там, где его передали.

HTTP коды ответов ​

КодОписаниеИспользование
200OKУспешный запрос
201CreatedОбъект создан: адреса покупателя
400Bad RequestБитый запрос без карты полей
401UnauthorizedНе авторизован
403ForbiddenНет прав доступа
404Not FoundРоут или ресурс не найден
405Method Not AllowedМетод не разрешён. Сюда же попадает preflight OPTIONS, если в стеке роута нет CorsMiddleware
409ConflictКонфликт состояния: нехватка остатка при правке состава заказа
422Unprocessable EntityОшибка валидации с картой полей в errors
429Too Many RequestsПревышен лимит запросов
500Internal Server ErrorВнутренняя ошибка
503Service UnavailableСервис ms3 не поднялся

Middleware ​

Middleware выполняются до обработчика роута, в том порядке, в котором перечислены. Если middleware возвращает Response, цепочка обрывается: остальные middleware и обработчик не выполняются.

Интерфейс ​

php
namespace MiniShop3\Router\Middleware;

interface MiddlewareInterface
{
    /**
     * @param array $params URL параметры
     * @return Response|null Response для прерывания, null для продолжения
     */
    public function handle(array $params);
}

Встроенные middleware ​

AuthMiddleware ​

Проверяет авторизацию пользователя MODX. Для контекста mgr ещё проверяет токен HTTP_MODAUTH.

php
use MiniShop3\Router\Middleware\AuthMiddleware;

// Проверка авторизации в админке
$router->get('/api/mgr/secure', function($params) use ($modx) {
    return Response::success(['user' => $modx->user->get('username')]);
}, [
    new AuthMiddleware($modx, 'mgr')  // 'mgr' или 'web'
]);

PermissionMiddleware ​

Проверяет права доступа пользователя через $modx->hasPermission().

php
use MiniShop3\Router\Middleware\PermissionMiddleware;

$router->post('/api/mgr/products', function($params) use ($modx) {
    // Создание товара
}, [
    new PermissionMiddleware($modx, 'msproduct_save')
]);

Права MiniShop3. Шаблон политики miniShopManagerPolicy объявляет пятнадцать собственных прав; к ним добавляется view_document из ядра MODX.

ПравоЧто открываетГде проверяется
msproduct_saveсоздание и правка товаров, сортировка, правка данных товара в гридероуты
msproduct_publishпубликация товара из грида категориироуты
msproduct_deleteмассовое удаление товаров категориироуты
mscategory_saveопции категории целикомроуты
msorder_listсписки заказов и покупателей, адреса покупателя, группы покупателейроуты
msorder_viewкарточка покупателя, группа покупателей по IDроуты
msorder_saveправка заказов и покупателей, мутации позиций заказа, отгрузкароуты
msorder_removeудаление заказов, покупателей, групп, массовое удалениероуты
msorder_inventory_overrideправка состава заказа в обход нехватки остатка (с 1.14.3-beta1)сервис
mssetting_listчтение справочных данных формроуты, AnyPermissionMiddleware
mssetting_viewто же, просмотрроуты, AnyPermissionMiddleware
mssetting_saveдоставки, оплаты, производители, статусы, типы связей, опции, свои поля, конфигурация гридовроуты
msproductfile_generateмассовые операции с галереейроуты
msproductfile_listчтение галереи товарапроцессоры
msproductfile_saveзагрузка, правка и удаление файлов галереипроцессоры
view_documentсправочники, грид товаров категории, конфигурация гридов на чтениероуты

Два права галереи в карту роутов не входят: их проверяют процессоры Processors\Gallery\*, которые вызывает UtilitiesGalleryController.

Право на правку в минус выдаётся отдельно

При включённом учёте остатков (ms3_inventory_enabled) правка состава заказа сверяет резерв с итоговым количеством и отказывает, если остатка не хватает: ответ 409 с ключом ms3_err_inventory_short. Если в запросе пришёл force, а права на это нет, ответ 403. В обоих случаях в data приходят product_id, available, required и can_override — последний показывает, поможет ли повтор с force.

msorder_inventory_override позволяет провести такую правку всё равно — остаток уходит в минус. Право есть в шаблоне политики, но в самой политике по умолчанию не выдано, поэтому после обновления его нужно назначить явно. Пользователь с sudo получает его автоматически, как и любое другое право MODX.

Откуда эти права берутся у пользователя

Все они лежат в политике доступа miniShopManagerPolicy, которую создаёт установка. Сама политика ничего не даёт, пока не выдана группе пользователей. С 1.14.2-beta1 установка и обновление выдают её всем группам, у которых уже есть доступ к контексту mgr, — добавочной записью с authority 9999, так что собственные права группы остаются главнее.

Группа, созданная после установки, политику не получит: резолвер работает только в момент установки или обновления. Подробности — в быстром старте.

AnyPermissionMiddleware ​

Пускает запрос, если у пользователя есть хотя бы одно право из списка. Нужен там, где точную проверку делает контроллер: пакетные действия над товарами категории проверяют право на каждое действие отдельно.

php
use MiniShop3\Router\Middleware\AnyPermissionMiddleware;

$router->post('/multiple', $handler, [
    new AnyPermissionMiddleware($modx, ['msproduct_save', 'msproduct_publish', 'msproduct_delete'])
]);

ServiceCheckMiddleware ​

Проверяет, что сервис ms3 поднялся. Если нет — отвечает 503 с error_code: internal_error, не доходя до обработчика. В стеке Web API стоит третьим из трёх групповых — после CorsMiddleware и RateLimitMiddleware, но до маршрутного TokenMiddleware. Отсюда следствие: если сервис ms3 не поднялся, запрос всё равно съедает счётчик лимита.

TokenMiddleware ​

Проверяет токен покупателя для Web API и при необходимости создаёт гостевой токен.

Порядок определения токена: Authorization: Bearer → заголовок MS3TOKEN (устаревший) → httpOnly cookie ms3_token → $_REQUEST → session. Query-параметр ms3_token middleware удаляет из запроса; token в query остаётся, но и не авторизует — им пользуется подтверждение e-mail.

php
use MiniShop3\Middleware\TokenMiddleware;

$tokenMiddleware = new TokenMiddleware($modx);

$router->group('/api/v1/cart', function($router) use ($modx) {
    // Роуты корзины
}, [$tokenMiddleware]);

В самом middleware есть список publicRoutes из пятнадцати префиксов и один шаблон пути. Путь из списка проходит без гостевого токена и без 401. Отдельного режима optional в коде нет: поведение определяет попадание пути в этот список.

Список не зашит намертво. Свой публичный роут делают так: создают собственный экземпляр middleware, расширяют список и передают его в группу.

php
$tokenMiddleware = new TokenMiddleware($modx);
$tokenMiddleware->addPublicRoute('/api/v1/myaddon/callback');
// или заменить список целиком: setPublicRoutes([...])

Preflight OPTIONS middleware пропускает насквозь: токен не выпускается, сессия не поднимается, обработчик не вызывается.

text
/api/v1/product/get          /api/v1/delivery/get
/api/v1/product/list         /api/v1/delivery/list
/api/v1/product/filters      /api/v1/delivery/webhook
/api/v1/category/get         /api/v1/payment/get
/api/v1/category/list        /api/v1/payment/list
/api/v1/category/tree        /api/v1/payment/webhook/
/api/v1/health               /api/v1/customer/token/get
                             /api/v1/customer/logout

Шаблон — /api/v1/product/*/images, где * закрывает ровно один сегмент: так галерея товара остаётся публичной, не открывая всю ветку /api/v1/product/*.

Совпадение проверяется как путь === префикс или путь начинается с префикса и слэша. У записи оплаты слэш на конце уже есть, поэтому код сверяет её с двумя слэшами подряд и /api/v1/payment/webhook/{id} под неё не попадает никогда (разбор).

На практике список работает на двух путях: GET /customer/token/get и POST /customer/logout — только на этих публичных путях middleware и стоит. Каталог, health и вебхуки в web.php его вообще не подключают.

Подробнее: Авторизация Web API.

CorsMiddleware ​

Настраивает заголовки CORS для запросов с другого домена.

php
use MiniShop3\Middleware\CorsMiddleware;

$corsMiddleware = new CorsMiddleware([
    'allowed_origins' => ['https://shop.example.com', 'https://admin.example.com'],
    'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
    'allowed_headers' => ['Content-Type', 'Authorization', 'MS3TOKEN'],
    'allow_credentials' => true,
    'max_age' => 86400  // Кеш preflight запросов (24 часа)
]);

Системная настройка: ms3_cors_allowed_origins. Пусто = same-origin. * = любой origin без credentials. Список доменов нужен для cookie с другого origin. См. CORS.

RateLimitMiddleware ​

Ограничивает число запросов.

php
use MiniShop3\Middleware\RateLimitMiddleware;
use MiniShop3\Services\RateLimit\RateLimitStoreFactory;

$rateLimitMiddleware = new RateLimitMiddleware(
    60,                                      // Максимум 60 запросов
    60,                                      // За 60 секунд
    RateLimitStoreFactory::fromModx($modx)   // Хранилище лимитов (опционально)
);

Системные настройки:

  • ms3_rate_limit_max_attempts — максимум запросов (по умолчанию: 60)
  • ms3_rate_limit_decay_seconds — период в секундах (по умолчанию: 60)

Заголовки ответа:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1703001234

Создание своего middleware ​

php
<?php
namespace MyComponent\Middleware;

use MiniShop3\Router\Middleware\MiddlewareInterface;
use MiniShop3\Router\Response;
use MODX\Revolution\modX;

class LoggingMiddleware implements MiddlewareInterface
{
    private modX $modx;

    public function __construct(modX $modx)
    {
        $this->modx = $modx;
    }

    public function handle(array $params)
    {
        // Логирование запроса
        $this->modx->log(
            modX::LOG_LEVEL_INFO,
            sprintf(
                '[API] %s %s from %s',
                $_SERVER['REQUEST_METHOD'],
                $_SERVER['REQUEST_URI'],
                $_SERVER['REMOTE_ADDR']
            )
        );

        // null = продолжить выполнение
        return null;
    }
}

Использование:

php
use MyComponent\Middleware\LoggingMiddleware;

$router->get('/api/mgr/logged-endpoint', function($params) use ($modx) {
    return Response::success(['logged' => true]);
}, [
    new LoggingMiddleware($modx),
    new AuthMiddleware($modx, 'mgr')
]);

Порядок выполнения middleware ​

Порядок в массиве — он же порядок выполнения:

php
$router->get('/api/mgr/endpoint', $handler, [
    new CorsMiddleware(),                               // 1. Сначала CORS
    new RateLimitMiddleware(),                          // 2. Проверка лимита
    new AuthMiddleware($modx),                          // 3. Авторизация
    new PermissionMiddleware($modx, 'msproduct_save'),  // 4. Право доступа
]);

Карта роутов ​

Manager API (/api/mgr/*) ​

Общие ​

МетодРоутОписание
GET/healthПроверка работоспособности API
GET/user/infoИнформация о текущем пользователе

Справочники (/references) ​

Чтение справочников, право view_document. Все восемь роутов группы — GET: они обслуживают автодополнение и выбор значений в формах админки.

МетодРоутОписание
GET/vendorsСписок производителей
GET/autocompleteАвтодополнение товаров
GET/optionsСписок опций
GET/product-option-fieldsПоля опций товара
GET/product-field-valuesЗначения полей товара
GET/productsПоиск/автодополнение товаров
GET/link-typesТипы связей (msLink)
GET/customersАвтодополнение/поиск покупателей

Поля модели (/models/{alias}/fields) ​

Формы товара и заказа берут отсюда конфигурацию полей.

МетодРоутОписание
GET/{alias}/fieldsПоля модели по alias. Отдельного права нет, достаточно сессии mgr

Свои поля (/extra-fields) ​

CRUD своих полей для заказов и товаров. Чтение — без mssetting_save, запись — с этим правом.

МетодРоутОписание
GET/Получить список дополнительных полей
GET/{id}Получить дополнительное поле по ID
POST/Создать дополнительное поле
PUT/{id}Обновить дополнительное поле
DELETE/{id}Удалить дополнительное поле

Настройка опций (/options) ​

CRUD опций настроек магазина. Право mssetting_save.

МетодРоутОписание
GET/typesТипы опций
GET/treeДерево опций
GET/suggestionsПредложения опций
POST/bulk/assignМассовое назначение опций
DELETE/bulkМассовое удаление опций
POST/Создать опцию
GET/Получить список опций
GET/{id}Получить опцию по ID
PUT/{id}Обновить опцию
DELETE/{id}Удалить опцию

Группы опций (/option-groups) ​

Группировка опций вместо прежнего способа на modCategory. Право mssetting_save.

МетодРоутОписание
GET/Получить список групп опций
POST/Создать группу опций
GET/{id}Получить группу опций по ID
PUT/{id}Обновить группу опций
DELETE/{id}Удалить группу опций
DELETE/bulkМассовое удаление
PUT/positionsОбновить позиции (сортировка)

Группы покупателей (/customer-groups) ​

Связь с группами пользователей MODX для ACL каталога. Чтение: msorder_list. Запись: msorder_save.

МетодРоутОписаниеПраво
GET/Получить список групп покупателейmsorder_list
POST/Создать группу покупателейmsorder_save
GET/{id}Получить группу покупателейmsorder_view
PUT/{id}Обновить группу покупателейmsorder_save
DELETE/{id}Удалить группу покупателейmsorder_remove

Опции категории (/categories/{category_id}/options) ​

Опции конкретной категории. Право на всю группу — mscategory_save.

МетодРоутОписание
GET/Получить список опций категории
POST/Создать опцию категории
POST/sortСортировка опций категории
POST/bulkМассовые операции с опциями категории
POST/duplicateДублирование опции
PUT/{option_id}Обновить опцию категории
DELETE/{option_id}Удалить опцию категории

Товары категории (/categories/{id}/products) ​

МетодРоутОписаниеПраво
GET/{id}/productsСписок товаров категорииview_document
GET/{id}/products/filtersКонфигурация фильтровview_document
POST/{id}/products/sortСортировкаmsproduct_save
DELETE/{id}/products/bulkМассовое удалениеmsproduct_delete
POST/{id}/products/multipleПакетные действия. Маршрут пускает любое право мутации товара, точную проверку делает контроллерсм. CategoryProductActionPermissions
PUT/{id}/products/{productId}/dataПравка данных товараmsproduct_save
POST/{id}/products/{productId}/publishПубликацияmsproduct_publish

Конфигурация (/config) ​

МетодРоутОписание
GET/page-fields/{page_key}Получить поля страницы (без mssetting_save)
GET/page-fields/{page_key}/allВсе поля страницы
PUT/page-fields/{page_key}Обновить поля (тело: { "fields": [...] }, нужно mssetting_save)
GET/sections/{page_key}Получить секции
PUT/sections/{page_key}Обновить секции (тело: { "sections": [...] })
DELETE/sections/{page_key}/{section_key}Удалить секцию по ключу

Конфигурация гридов (/grid-config) ​

CRUD колонок административных гридов. Чтение: view_document. Запись: mssetting_save.

МетодРоутОписание
GET/{grid_key}Получить конфигурацию грида. Параметр include_hidden=1 добавляет скрытые колонки — в этом режиме конфигурацию читает редактор
PUT/{grid_key}Обновить конфигурацию грида (тело: fields)
POST/{grid_key}/fieldДобавить колонку
PUT/{grid_key}/field/{field_name}Обновить колонку
DELETE/{grid_key}/{field_name}Удалить колонку

Конфигурация в базе есть у шести гридов: orders, order_products, customers, vendors, category-products, deliveries — их колонки заводят сид-миграции. Грид платежей запрашивает ключ payments, но сида для него нет: ответ приходит пустым, и грид рисует встроенные колонки. В списке гридов в настройках доступны пять ключей, без deliveries и payments.

Ответ GET /grid-config/{grid_key} ​
json
{
    "columns": [
        { "name": "id", "label": "ID", "type": "model", "visible": true, ... }
    ],
    "direct_filter_keys": ["query", "status_id", "delivery_id", ...],
    "editor_references": [
        { "key": "vendors", "path": "/api/mgr/references/vendors" }
    ]
}
ПолеТипОписание
columnsarrayКолонки грида с конфигурацией (тип, видимость, фильтрация, редактор)
direct_filter_keysstring[]Ключи фильтров, которые контроллер ждёт как прямые параметры запроса (без префикса filter_). Остальные отправляют с префиксом. Источник истины — сервер: клиент читает список отсюда, поэтому новый фильтр не нужно дублировать в его коде. Появилось в MiniShop3 1.12.0.
editor_referencesarray<{key,path}>Только для grid_key=category-products. Список допустимых ключей справочников для выпадающего списка в combo-редакторе колонки. Каждая запись — пара ключа и пути к эндпоинту справочника. Используется в интерфейсе настройки колонок. Появилось в MiniShop3 1.12.0.
Контракт фильтров (direct_filter_keys) ​

Клиент решает, как сериализовать значение фильтра:

js
// Псевдокод composable useGridFilterParams
function addFilterParam(params, key, value) {
    if (directFilterKeys.has(key)) {
        params[key] = value         // direct: ?status_id=2
    } else {
        params['filter_' + key] = value   // prefixed: ?filter_customer_name=...
    }
}

Поэтому при добавлении прямого фильтра на сервере обязательно дописывайте его ключ в константу DIRECT_FILTER_KEYS соответствующего контроллера. Иначе клиент отправит фильтр с префиксом и фильтрация не сработает.

Для нового грида одной константы мало: GridConfigController отдаёт direct_filter_keys только тем ключам, что перечислены в его списке провайдеров — сейчас там заказы и покупатели. У заказов сама константа лежит в ManagerOrderListService, а контроллер на неё ссылается.

Заказы (/orders) ​

Чтение и запись разделены на две группы middleware.

Чтение, право msorder_list:

МетодРоутОписание
GET(корень)Список заказов
GET/filtersКонфигурация фильтров
GET/statsАгрегаты для дашборда и фильтров
GET/{id}Получить заказ
GET/{id}/productsТовары заказа
GET/{id}/logsИстория изменений

Запись, право msorder_save:

МетодРоутОписание
POST(корень)Создать заказ из админки
DELETE/bulkМассовое удаление
POST/{id}/finalizeФинализация черновика
POST/{id}/recalculate-costПересчёт стоимости
PUT/{id}Обновить заказ
DELETE/{id}Удалить заказ
POST/{id}/productsДобавить товар
PUT/{id}/products/{product_id}Обновить позицию
DELETE/{id}/products/{product_id}Удалить позицию

Отгрузки заказа:

МетодРоутОписаниеПраво
GET/{id}/shipmentПолучить данные отгрузки заказаmsorder_list
PUT/{id}/shipmentСохранить/обновить отгрузку заказаmsorder_save

Справочники для форм заказа (без отдельного PermissionMiddleware, достаточно сессии mgr). Роуты объявлены на уровне группы /api/mgr, а не внутри /orders, поэтому путь указан целиком:

МетодРоутОписание
GET/api/mgr/statuses-dropdownСтатусы с переводами
GET/api/mgr/deliveries-activeАктивные доставки для select
GET/api/mgr/payments-activeАктивные способы оплаты для select

Поля моделей (/model-fields) ​

Метаданные полей моделей для динамических форм. Чтение без специального права, запись: mssetting_save.

Чтение (метаданные):

МетодРоутОписание
GET/modelsСписок доступных моделей
GET/sections/{model}Секции модели
GET/Список полей (параметры: model, section)
GET/{id}Получить поле по ID

Чтение (данные с combo-options):

МетодРоутОписаниеПраво
GET/visible/{model}Видимые поля моделилюбое из списка ниже
GET/combo-options/{model}Combo-options моделилюбое из списка ниже
GET/combo-options/{model}/{field_name}Combo-options конкретного полялюбое из списка ниже

AnyPermissionMiddleware на этих трёх GET: msorder_list, msorder_view, msorder_save, msproduct_save, mscategory_save, mssetting_list, mssetting_view, mssetting_save, view_document.

Запись, право mssetting_save:

МетодРоутОписание
POST/sectionsСоздать секцию
PUT/sections/ranksПорядок секций
PUT/sections/{id}Обновить секцию
DELETE/sections/{id}Удалить секцию
POST(корень)Создать поле
PUT/ranksПорядок полей
PUT/{id}Обновить поле
DELETE/{id}Удалить поле

Покупатели (/customers) ​

МетодРоутОписаниеПраво
GET(корень)Список покупателейmsorder_list
DELETE/bulkМассовое удалениеmsorder_remove
GET/{id}Получить покупателяmsorder_view
PUT/{id}Обновить покупателяmsorder_save
DELETE/{id}Удалить покупателяmsorder_remove
GET/{id}/addressesАдреса покупателяmsorder_list
POST/{id}/addressesДобавить адресmsorder_save
PUT/{id}/addresses/{address_id}Обновить адресmsorder_save
DELETE/{id}/addresses/{address_id}Удалить адресmsorder_remove

Настройки магазина ​

Доставки (/deliveries) ​

CRUD способов доставки. Право mssetting_save.

МетодРоутОписание
GET/Получить список способов доставки
POST/Создать способ доставки
DELETE/bulkМассовое удаление доставок
POST/sortСортировка доставок
PUT/positionsОбновить позиции доставок
GET/{id}Получить доставку по ID
PUT/{id}Обновить доставку
DELETE/{id}Удалить доставку
GET/{id}/paymentsСпособы оплаты для доставки
POST/{id}/paymentsДобавить способ оплаты доставки
DELETE/{id}/payments/{payment_id}Удалить способ оплаты доставки
Платежи (/payments) ​

CRUD способов оплаты. Право mssetting_save.

МетодРоутОписание
GET/Получить список способов оплаты
POST/Создать способ оплаты
DELETE/bulkМассовое удаление способов оплаты
POST/sortСортировка способов оплаты
PUT/positionsОбновить позиции способов оплаты
GET/{id}Получить способ оплаты по ID
PUT/{id}Обновить способ оплаты
DELETE/{id}Удалить способ оплаты
GET/{id}/deliveriesСпособы доставки для способа оплаты
POST/{id}/deliveriesДобавить доставку к способу оплаты
DELETE/{id}/deliveries/{delivery_id}Удалить доставку из способа оплаты
Производители (/vendors) ​

CRUD производителей. Право mssetting_save.

МетодРоутОписание
GET/Получить список производителей
POST/Создать производителя
DELETE/bulkМассовое удаление производителей
POST/sortСортировка производителей
GET/{id}Получить производителя по ID
PUT/{id}Обновить производителя
DELETE/{id}Удалить производителя
Статусы (/statuses) ​

CRUD статусов заказов. Право mssetting_save.

МетодРоутОписание
GET/Получить список статусов
POST/Создать статус
DELETE/bulkМассовое удаление статусов
POST/sortСортировка статусов
GET/{id}Получить статус по ID
PUT/{id}Обновить статус
DELETE/{id}Удалить статус

CRUD типов связей товаров. Право mssetting_save.

МетодРоутОписание
GET/Получить список типов связей
POST/Создать тип связей
DELETE/bulkМассовое удаление типов связей
GET/typesПолучить типы связей (алиас для обратной совместимости)
GET/{id}Получить тип связи
PUT/{id}Обновить тип связи
DELETE/{id}Удалить тип связи

Данные товара (/product-data) ​

Вкладки «Категории» и «Связи» на карточке товара (Vue). Право msproduct_save / сессия mgr:

МетодРоутОписание
GET/{id}Данные товара
PUT/{id}Сохранить данные товара
GET/{id}/categories/treeДерево категорий (ms3_product_category_tree)
GET/{id}/linksСписок связей
POST/{id}/linksДобавить связь { slave, link }
DELETE/{id}/linksУдалить связь { link, master, slave }
GET/references/link-typesТипы msLink (группа references)
GET/references/productsАвтодополнение товаров

Уведомления (/notifications) ​

МетодРоутОписаниеПраво
GET/referencesСправочники для формmssetting_save
GET(корень)Список уведомленийmssetting_save
GET/{id}Получить уведомлениеmssetting_save
POST(корень)Создать уведомлениеmssetting_save
PUT/{id}Обновитьmssetting_save
DELETE/{id}Удалитьmssetting_save

Импорт (/import) ​

МетодРоутОписаниеПраво
GET/fieldsПоля для маппингаmsproduct_save
POST/uploadЗагрузка CSVmsproduct_save
POST/previewПредпросмотрmsproduct_save
POST/startЗапуск импортаmsproduct_save
GET/progress/{import_id}Прогресс импортаmsproduct_save

Галерея (/utilities/gallery) ​

МетодРоутОписаниеПраво
POST/updateМассовые операции с галереейmsproductfile_generate

Web API (/api/v1/*) ​

Полная таблица путей: Web API → Карта эндпоинтов.

Руководства: auth, catalog, cart, checkout, customer.

Свои роуты ​

Добавление своих роутов ​

Создайте файл core/config/ms3_routes_manager.custom.php:

php
<?php
use MiniShop3\Router\Middleware\AuthMiddleware;
use MiniShop3\Router\Middleware\PermissionMiddleware;
use MiniShop3\Router\Response;

// Простой роут
$router->get('/api/mgr/my-custom-endpoint', function() use ($modx) {
    return Response::success(['custom' => true]);
}, [
    new AuthMiddleware($modx, 'mgr')
]);

// Группа роутов
$router->group('/api/mgr/my-module', function($router) use ($modx) {

    $router->get('/dashboard', function() use ($modx) {
        return Response::success([
            'stats' => ['orders' => 100, 'revenue' => 50000]
        ]);
    });

    $router->post('/action', function($params) use ($modx) {
        $data = json_decode(file_get_contents('php://input'), true);
        // Обработка...
        return Response::success(['processed' => true]);
    });

}, [
    new AuthMiddleware($modx, 'mgr'),
    new PermissionMiddleware($modx, 'my_module_permission')
]);

Для Web API создайте core/config/ms3_routes_web.custom.php.

Переопределение системных роутов ​

Свои роуты загружаются после системных и перекрывают их:

php
<?php
// core/config/ms3_routes_manager.custom.php

use MiniShop3\Router\Response;

// Переопределение системного роута /api/mgr/health
$router->get('/api/mgr/health', function() use ($modx) {
    return Response::success([
        'status' => 'custom_ok',
        'version' => '1.0.0-custom',
        'timestamp' => time()
    ]);
});

Интеграция со сторонними компонентами ​

Сторонний компонент добавляет роуты тем же файлом — но так делают только при ручной правке сайта. Распространяемый аддон кладёт свой файл в ms3.routes.d/, см. раздел ниже.

php
<?php
// core/config/ms3_routes_manager.custom.php

use MiniShop3\Router\Response;
use MiniShop3\Router\Middleware\AuthMiddleware;

// Роуты для компонента msPromoCode
$router->group('/api/mgr/promocodes', function($router) use ($modx) {

    $router->get('', function($params) use ($modx) {
        $codes = $modx->getCollection('msPromoCode');
        $result = [];
        foreach ($codes as $code) {
            $result[] = $code->toArray();
        }
        return Response::success(['codes' => $result]);
    });

    // POST '' и DELETE '/{id}' описываются так же: тело запроса читается
    // из php://input, ответ собирается через Response::success()
    // или Response::error() с нужным кодом

}, [
    new AuthMiddleware($modx, 'mgr')
]);

Модульные роуты для аддонов (ms3.routes.d) ​

Файлы ms3_routes_*.custom.php подходят для ручной правки сайта, но не для аддонов. Если два компонента пишут в один файл, при установке и удалении возникают конфликты.

Для сторонних компонентов есть директория core/config/ms3.routes.d/. Каждый аддон создаёт свой файл, конфликтов нет.

Тот же приём есть у сервисов

Каталог ms3.services.d/ для сервисов устроен так же.

Структура ​

core/config/ms3.routes.d/
├── web/                          # Роуты Web API (api.php)
│   ├── 50-mydelivery.php
│   └── 50-mypayment.php
└── manager/                      # Роуты Manager API (connector)
    ├── 50-mydelivery.php
    └── 50-myadmin.php

Файлы загружаются в алфавитном порядке. Приоритет задаёт числовой префикс (01-, 50-, 99-).

Порядок загрузки ​

Web API (api.php):

  1. config/routes/web.php — системные роуты
  2. core/config/ms3_routes_web.custom.php — ручные правки
  3. core/config/ms3.routes.d/web/*.php — аддоны
  4. build() диспетчера

Manager API (connector.php → Processors\Api\Router):

  1. config/routes/manager.php — системные роуты
  2. core/config/ms3_routes_manager.custom.php — ручные правки
  3. core/config/ms3.routes.d/manager/*.php — аддоны
  4. build() диспетчера

Каждый следующий уровень перекрывает роуты предыдущего по ключу METHOD:PATTERN.

Пример: Web API роуты аддона доставки ​

php
<?php
// core/config/ms3.routes.d/web/50-mydelivery.php

use MiniShop3\Router\Response;
use MiniShop3\Middleware\TokenMiddleware;

$router->group('/api/v1/delivery/mydelivery', function ($router) use ($modx) {

    $router->post('/calculate', function (array $vars, \MODX\Revolution\modX $modx) {
        $input = json_decode(file_get_contents('php://input'), true) ?: [];
        $cost = MyDelivery::calculate($input['address'] ?? '', $input['weight'] ?? 0);
        return Response::success(['cost' => $cost]);
    });

    $router->get('/points', function (array $vars, \MODX\Revolution\modX $modx) {
        $city = $vars['city'] ?? '';
        $points = MyDelivery::getPickupPoints($city);
        return Response::success(['points' => $points]);
    });

}, [new TokenMiddleware($modx)]);

Пример: Manager API роуты аддона ​

php
<?php
// core/config/ms3.routes.d/manager/50-mydelivery.php

use MiniShop3\Router\Middleware\AuthMiddleware;
use MiniShop3\Router\Response;

$router->group('/api/mgr/mydelivery', function ($router) use ($modx) {

    $router->get('/settings', function (array $vars, \MODX\Revolution\modX $modx) {
        return Response::success([
            'api_key' => $modx->getOption('mydelivery_api_key'),
            'enabled' => (bool) $modx->getOption('mydelivery_enabled'),
        ]);
    });

    $router->put('/settings', function (array $vars, \MODX\Revolution\modX $modx) {
        $data = json_decode(file_get_contents('php://input'), true) ?: [];
        // Сохранение настроек...
        return Response::success(['saved' => true]);
    });

}, [new AuthMiddleware($modx, 'mgr')]);

Установка и удаление ​

В резолвере аддона:

php
<?php
// resolver при установке
$routesDir = MODX_CORE_PATH . 'config/ms3.routes.d/';

// Копируем файл роутов
copy(
    $source . 'routes/web.php',
    $routesDir . 'web/50-mydelivery.php'
);
copy(
    $source . 'routes/manager.php',
    $routesDir . 'manager/50-mydelivery.php'
);
php
<?php
// resolver при удалении
@unlink(MODX_CORE_PATH . 'config/ms3.routes.d/web/50-mydelivery.php');
@unlink(MODX_CORE_PATH . 'config/ms3.routes.d/manager/50-mydelivery.php');

Каждый аддон удаляет только свой файл, чужие не трогает.

Обработка ошибок ​

Файл с синтаксической ошибкой или исключением пропускается, причина уходит в журнал. Остальные файлы загружаются как обычно:

[MiniShop3 Router] Failed to load routes file /path/to/50-broken.php: syntax error...

Custom-файл vs ms3.routes.d ​

ms3_routes_*.custom.phpms3.routes.d/
Для когоРазработчик сайтаАвторы аддонов
ФайловОдин на тип APIПо файлу на аддон
КонфликтыВозможны при нескольких аддонахНет
Установка/удалениеПравить вручнуюАтомарно: создать или удалить файл
СоздаётсяManager-файл создаёт установка MiniShop3 из примера. Web-файл она не создаёт: заготовки в пакете нет, сделайте файл самиАддон, через резолвер

Системные настройки ​

НастройкаПо умолчаниюОписание
ms3_cors_allowed_originsпустая строкаРазрешённые домены для CORS. Пусто = same-origin
ms3_rate_limit_max_attempts60Лимит запросов
ms3_rate_limit_decay_seconds60Период сброса лимита (сек)

Отладка ​

Логирование запросов ​

php
$router->get('/api/mgr/debug', function($params) use ($modx) {
    $modx->log(
        \MODX\Revolution\modX::LOG_LEVEL_INFO,
        '[API Debug] ' . json_encode([
            'method' => $_SERVER['REQUEST_METHOD'],
            'uri' => $_SERVER['REQUEST_URI'],
            'params' => $params,
            'body' => file_get_contents('php://input')
        ])
    );

    return Response::success(['debug' => true]);
});

Типичные ошибки ​

401 Unauthorized:

  • Не авторизован в MODX
  • Отсутствует или невалидный HTTP_MODAUTH токен
  • Для Web API: отсутствует или истёк MS3TOKEN

403 Forbidden:

  • Нет права доступа (проверьте ACL пользователя)

404 Not Found:

  • Роут не зарегистрирован
  • Опечатка в URL

405 Method Not Allowed:

  • Выбран не тот HTTP-метод: GET вместо POST и так далее

429 Too Many Requests:

  • Превышен лимит запросов, подождите Retry-After секунд