Skip to content

Обработка отсутствия сервиса ms3 (Issue #68)#99

Merged
biz87 merged 3 commits intobetafrom
feature/68-services-has-check
Feb 20, 2026
Merged

Обработка отсутствия сервиса ms3 (Issue #68)#99
biz87 merged 3 commits intobetafrom
feature/68-services-has-check

Conversation

@Ibochkarev
Copy link
Copy Markdown
Member

@Ibochkarev Ibochkarev commented Feb 17, 2026

Описание

Безопасная обработка отсутствия сервиса ms3 (Issue #68): перед $modx->services->get('ms3') выполняется проверка has('ms3'). При отсутствии сервиса возвращается 503 или пустой вывод вместо необработанного Exception и 500.

Основные изменения:

  • Точки входа: api.php, connector.php — при !has('ms3') лог, HTTP 503, JSON-ответ. api.php: fallback-регистрация сервиса заменена на 503 (изменение поведения: при сбое bootstrap — 503 вместо попытки регистрации).
  • Плагин minishop3.php: OnMODXInit, OnManagerPageBeforeRender, OnLoadWebDocument — проверка has('ms3'), при отсутствии лог и break.
  • Сниппеты (ms3_cart, ms3_order, ms3_order_total, ms3_get_order, ms3_products, ms3_product_options, ms3_options, ms3_customer): проверка has('ms3'), при отсутствии лог и return ''.
  • Web API: ServiceCheckMiddleware — единая проверка has('ms3') на уровне роутера для всех маршрутов /api/v1. Контроллеры (Cart, Order, CustomerAddress) вызывают services->get('ms3') напрямую. Роуты token/get, profile, email/* — то же.
  • Response::error(): добавлено поле code в данные ошибки — Router выставляет корректный HTTP-код (в т.ч. 401, 503). Изменение API-контракта: поле code теперь в каждом error-ответе.
  • Fix CartController: change() и remove() возвращают ->getData() (массив), а не объект Response — соответствие типу : array.

Тип изменений

  • Исправление бага (non-breaking change)
  • Новая функциональность (non-breaking change)
  • Breaking change (изменение, ломающее обратную совместимость)
  • Рефакторинг (без изменения функциональности)
  • Документация
  • Другое (опишите):

Связанные Issues

Closes #68

Как это было протестировано?

  • Ручное тестирование (проверка потока при наличии/отсутствии сервиса)
  • Автоматические тесты (PHPStan, ESLint)
  • Тестирование на разных версиях PHP/MODX

Конфигурация тестирования:

  • MiniShop3: текущая ветка
  • MODX: 3.x
  • PHP: 8.x

Скриншоты (если применимо)

До После
500, необработанный Exception при отсутствии ms3 503 или пустой вывод, запись в лог

Чеклист

  • Код соответствует стилю проекта
  • Добавлены/обновлены комментарии в сложных местах
  • Изменения не ломают существующую функциональность
  • Лексиконы добавлены на двух языках (ru/en) — не требуется
  • PHPStan проходит без новых ошибок
  • ESLint проходит без ошибок (для JS/Vue изменений)
  • Обновлён CHANGELOG.md (для значимых изменений)

Дополнительные заметки

  • Реализация по комментарию biz87.
  • CustomerProfileController и CustomerEmailController получают $ms3 из роута (middleware гарантирует наличие).

- Проверка has('ms3') перед get() во всех точках входа (api.php, connector.php, плагин, сниппеты)
- Web API: трейт GetMs3OrFailTrait, ответ 503 при отсутствии сервиса
- Response::error(): добавлен code в data для корректного HTTP-кода в Router
- web.php: хелпер ensureMs3Service(), JSON 503 в connector

Co-authored-by: Cursor <[email protected]>
@Ibochkarev Ibochkarev requested a review from biz87 February 17, 2026 17:36
@Ibochkarev Ibochkarev self-assigned this Feb 17, 2026
Copy link
Copy Markdown
Member

@biz87 biz87 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Общая оценка

PR решает реальную проблему (Issue #68): $modx->services->get('ms3') бросает Exception если сервис не зарегистрирован, а проверка if ($ms3) после get() бесполезна — выполнение до неё не доходит. Подход has() + get() — правильный.

Проблемы

1. api.php: удалена fallback-регистрация сервиса

Текущий код имеет fallback:

if (!$modx->services->has('ms3')) {
    $modx->services->add('ms3', function() use ($modx) {
        return new \MiniShop3\MiniShop3($modx);
    });
}

PR заменяет это на 503-ошибку. api.php — самостоятельная точка входа, сервис регистрируется через bootstrap.php при $modx->initialize('web'). Если bootstrap отработал — fallback не нужен. Если не отработал — 503 оправдан.

Но это изменение поведения, стоит упомянуть в описании PR.

2. Response::error() + поле code — глобальный побочный эффект

Добавление 'code' => $statusCode в Response::error() влияет на ВСЕ API-ошибки, не только на 503.

Router::normalizeResponse() уже имеет $statusCode = $result['code'] ?? 400. До PR: Response::error('Token is required', 401)->getData() не содержал code → HTTP всегда 400. После PR: содержит 'code' => 401 → HTTP 401.

Это правильное исправление, но затрагивает всю систему. Стоит вынести в отдельный коммит или документировать как отдельный fix.

3. Исправлены баги в CartController (не упомянуто)

CartController::change() и CartController::remove() возвращают Response::error(...) (объект Response) вместо Response::error(...)->getData() (массив), нарушая тип : array. PR молча исправляет это — стоит отметить как отдельный fix.

4. Бойлерплейт: 4 строки × 15+ методов

$ms3 = $this->getMs3OrFail();
if ($ms3 === null) {
    return $this->serviceUnavailableResponse();
}

Повторяется ~15 раз. Альтернатива — middleware на уровне роутера, который проверяет has('ms3') до вызова контроллера. Одна проверка вместо пятнадцати.

5. Два паттерна для одной проверки

  • Контроллеры: GetMs3OrFailTrait
  • Роуты (web.php): замыкание $ensureMs3Service

Middleware решил бы оба случая единообразно.

6. code попадает в JSON-ответ клиенту

Поле code теперь будет в каждом error-ответе. Это изменение API-контракта — не критично, но стоит зафиксировать.

Что хорошо

  • Сниппеты и плагин — чистые проверки
  • Trait с понятным API
  • Логирование при отсутствии сервиса
  • connector.php — корректный Content-Type

Рекомендация

PR решает реальную проблему, но нужны доработки:

  1. Описать изменение поведения api.php
  2. Выделить fix Response::error code и CartController bugs — они самоценны вне issue #68
  3. Рассмотреть middleware вместо бойлерплейта в контроллерах

Copy link
Copy Markdown
Member

@biz87 biz87 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Дополнение к ревью: middleware вместо бойлерплейта в контроллерах

Конкретное предложение по пункту 4-5. Все контроллеры и inline closures в web.php проходят через роутер, который уже поддерживает middleware (см. AuthMiddleware, RateLimitMiddleware).

Middleware:

<?php
namespace MiniShop3\Router\Middleware;

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

class ServiceCheckMiddleware implements MiddlewareInterface
{
    protected modX $modx;

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

    public function handle(array $params)
    {
        if (!$this->modx->services->has('ms3')) {
            $this->modx->log(modX::LOG_LEVEL_ERROR, '[MiniShop3] Service not registered');
            return Response::error('Service unavailable', 503);
        }
        return null;
    }
}

Применение в web.php — одна строка:

$serviceCheckMiddleware = new ServiceCheckMiddleware($modx);

$router->group('/api/v1', function($router) use (...) {
    // все роуты защищены
}, [$corsMiddleware, $rateLimitMiddleware, $serviceCheckMiddleware]);

Что это убирает:

  • GetMs3OrFailTrait — не нужен
  • $ensureMs3Service лямбда в web.php — не нужна
  • 4 строки × 15 методов = ~60 строк бойлерплейта в контроллерах — не нужны

Контроллеры остаются чистыми — services->get('ms3') безопасен, т.к. middleware уже гарантировал наличие сервиса. Как $tokenMiddleware гарантирует авторизацию.

Проверки в api.php, connector.php, плагине и сниппетах остаются как есть — они не проходят через роутер.

Address PR #99 review: eliminate 4-line boilerplate in ~15 methods.
Single has(ms3) check at router level instead of repeated checks in controllers.
Remove ensureMs3Service closure from web.php routes.

Refs #68

Co-authored-by: Cursor <[email protected]>
@Ibochkarev Ibochkarev requested a review from biz87 February 18, 2026 04:16
@Ibochkarev
Copy link
Copy Markdown
Member Author

Выделить fix Response::error code и CartController bugs — они самоценны вне issue #68 - оставил тут

@biz87
Copy link
Copy Markdown
Member

biz87 commented Feb 20, 2026

Ревью PR #99 (повторное)

Статус предыдущих замечаний

# Замечание Статус
1 api.php: удалена fallback-регистрация — изменение поведения ✅ Описано в PR description
2 Response::error code — глобальный побочный эффект ✅ Описано в PR description
3 CartController баги не упомянуты ✅ Описано в PR description
4 Бойлерплейт 4 строки × 15 методов ✅ Заменён на ServiceCheckMiddleware
5 Два паттерна (trait + closure) ✅ Единый паттерн через middleware
6 code в JSON — изменение API-контракта ✅ Описано в PR description

Все замечания учтены. Middleware — именно то, что предлагалось.

Новые замечания

1. CustomerAddressController: лишнее усложнение getAuthorizedCustomer (minor)

PR добавляет параметр $ms3 в getAuthorizedCustomer() и каждый из 6 методов-вызовов получает $ms3 только чтобы передать:

// Было (и работает корректно с middleware):
$customer = $this->getAuthorizedCustomer();

// Стало:
$ms3 = $this->modx->services->get('ms3');
$customer = $this->getAuthorizedCustomer($ms3);

$ms3 нигде больше в этих методах не используется. Middleware гарантирует has('ms3'), поэтому getAuthorizedCustomer() может безопасно вызывать services->get('ms3') внутри себя — как и раньше. Изменение добавляет 12 строк без пользы.

Предложение: откатить изменения в CustomerAddressController — оставить getAuthorizedCustomer() без параметра. Middleware уже защищает.

2. if (!$ms3) — мёртвый код (существовал до PR, minor)

$ms3 = $ms3 ?? $this->modx->services->get('ms3');
if (!$ms3) {       // ← никогда не сработает
    return null;
}

services->get() либо возвращает сервис, либо бросает NotFoundException. Null/false не возвращает. С middleware проверка тем более бессмысленна. Не блокер, но если уж трогать этот метод — стоит убрать.

3. Response::error code + CartController ->getData() — связанные исправления

Эти два изменения зависят друг от друга. До PR:

  • CartController::change() возвращал Response::error('...', 401) (объект Response) → normalizeResponse видел Response и сохранял HTTP 401
  • После PR: возвращает ->getData() (массив) → normalizeResponse берёт $result['code'] ?? 400

Без поля code в Response::error (второе изменение) HTTP-код регрессировал бы к 400 для всех ошибок. Исправления корректны, но тесно связаны — при черри-пике одного без другого поведение сломается.

Как автор и отметил — стоит вынести в отдельный коммит/PR. Не блокер.

Что хорошо

  • ServiceCheckMiddleware — чистое решение, консистентное с TokenMiddleware, RateLimitMiddleware, CorsMiddleware
  • Единый подход: middleware для API-роутов, inline-проверки для entry points / сниппетов / плагинов — каждый слой защищён на своём уровне
  • PR description — полностью обновлено, все изменения поведения задокументированы
  • Порядок middleware [$corsMiddleware, $rateLimitMiddleware, $serviceCheckMiddleware] — правильный: CORS-заголовки выставляются первыми (клиент сможет прочитать 503-ответ), rate limit до service check
  • Идемпотентностьhas() перед get() во всех точках

Вердикт

Approve с minor замечаниями. Основная задача (Issue #68) решена корректно. Замечание 1 (CustomerAddressController) — можно поправить перед мержем. Замечания 2-3 — не блокируют.

- Убрать параметр $ms3 из getAuthorizedCustomer() — middleware гарантирует has('ms3')
- Удалить мёртвый код if (!$ms3) — services->get() либо возвращает, либо бросает Exception
- Убрать 12 строк бойлерплейта в 6 методах

Co-authored-by: Cursor <[email protected]>
@biz87 biz87 merged commit febd747 into beta Feb 20, 2026
@Ibochkarev Ibochkarev deleted the feature/68-services-has-check branch February 20, 2026 18:51
@biz87 biz87 mentioned this pull request Feb 20, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Не обработанный Exception при вызове $modx->services->get(...)

2 participants