Skip to content

[Feature] кастомные поля msProductData в процессор create product #297

@rasxod

Description

@rasxod

Описание проблемы

При программном создании товара через процессор MiniShop3\Processors\Product\Create (или runProcessor с тем же классом) значения Object Extension-полей таблицы ms3_products (msProductData), созданных в утилите «Дополнительные поля» (например ext_id), не попадают в запись — даже если стандартные поля вроде price передаются в том же плоском $payload.

Пример из интеграции:

$payload = [
    'pagetitle' => $name,
    'parent' => $parentId,
    'context_key' => $contextKey,
    'class_key' => msProduct::class,
    'template' => $templateId,
    'published' => $product->get('active') ? 1 : 0,
    'price' => (float) $product->get('price'),
    'ext_id' => trim((string) $product->get('ext_id')),
];

$response = $this->modx->runProcessor('MiniShop3\\Processors\\Product\\Create', $payload);

Поле ext_id создано через утилиту дополнительных полей:

Image

Фактическое поведение

  • price и другие встроенные колонки msProductData из статической xPDO-карты обычно сохраняются.
  • Extra fields (ext_id и аналоги из ms3_extra_fields / Object Extension) при том же плоском формате не записываются (или кажется, что «нет способа передать»).

Ожидаемое поведение

  1. Должен быть явный и документированный способ передать любые поля msProductData — включая extra fields после loadMap().
  2. Контракт процессоров Create / Update должен быть предсказуемым для интеграций (импорт, синхронизация с внешней системой, cron, Scheduler).

Анализ текущей реализации (код)

Как устроено сейчас

Processors\Product\Create и Update в beforeSet() обрабатывают только:

  • поля options-*$ms3ProductFormOptions + saveOptions() в afterSave();
  • нормализацию vendor_id.

Отдельного разбора вложенного блока Data нет.

Почему «плоский» ext_id может не сработать

  1. msProduct::set() перенаправляет ключи, которых нет в modResource, в msProductData — но xPDO принимает только поля, присутствующие в _fieldMeta модели.
  2. Extra fields попадают в карту динамически через $ms3->loadMap() (OnMODXInit, плагин MiniShop3). Без актуальной карты set('ext_id', …) игнорируется.
  3. После создания поля в утилите может понадобиться сброс кэша extra fields (ms3/extra_fields_meta) — иначе в другом контексте (CLI, фоновая задача) карта без ext_id.
  4. Процессор MODX\Revolution\Processors\Resource\Create при fromArray() может отбрасывать ключи, которых нет в _fieldMeta самого ресурса (без ignoreInvalid), не доходя до логики msProduct::set() для extra fields.

Итого: для встроенных полей плоский payload часто «случайно работает», для Object Extension — ненадёжно и неочевидно.

Предложение: ключ Data

Для консистентности с join-алиасом Data в запросах и явного контракта API — поддержать вложенный массив:

$payload = [
    'pagetitle' => $name,
    'parent' => $parentId,
    'context_key' => $contextKey,
    'class_key' => msProduct::class,
    'template' => $templateId,
    'published' => $product->get('active') ? 1 : 0,

    // modResource-поля — как сейчас
    // msProductData — явный блок
    'Data' => [
        'price' => (float) $product->get('price'),
        'ext_id' => trim((string) $product->get('ext_id')),
        'article' => $article,
    ],
];

$response = $this->modx->runProcessor('MiniShop3\\Processors\\Product\\Create', $payload);

Черновик реализации

В Create::beforeSet() / Update::beforeSet() (до parent::beforeSet()):

  1. Если передан Data и это массив — для каждого ключа вызвать $this->setProperty($key, $value) или сразу $this->object->set($key, $value) после инициализации объекта.
  2. $this->unsetProperty('Data'), чтобы родительский процессор не получал «лишний» ключ.
  3. Перед применением — $this->modx->services->get('ms3')->loadMap() (если ещё не загружено), чтобы extra fields были в _fieldMeta.
  4. Симметрично поддержать в Update.

Обратная совместимость

  • Плоские ключи (price, article, …) не ломать — оставить текущее поведение.
  • Dataдополнительный, явный канал для msProductData, рекомендуемый для extra fields и интеграций.

Workaround до фикса

/** @var \MiniShop3\MiniShop3 $ms3 */
$ms3 = $this->modx->services->get('ms3');
$ms3->loadMap();

$response = $this->modx->runProcessor('MiniShop3\\Processors\\Product\\Create', $payload);
if ($response->isError()) {
    // ...
}

$id = (int) ($response->getObject()['id'] ?? 0);
if ($id > 0) {
    /** @var \MiniShop3\Model\msProduct $product */
    $product = $this->modx->getObject(\MiniShop3\Model\msProduct::class, $id);
    $data = $product->loadData();
    $data->set('ext_id', trim((string) $product->get('ext_id')));
    $data->save();
}

Либо после loadMap() попробовать плоский ключ в payload — если карта актуальна, msProduct::set('ext_id', …) может сработать.

Критерии приёмки

  • Create сохраняет extra field (ext_id) при передаче через Data['ext_id' => '…'].
  • Update — то же.
  • Плоский price / article по-прежнему работает.
  • Документация: пример runProcessor для товара с extra fields.
  • (Опционально) тест или smoke-сценарий импорта/синхронизации.

Связанный код

  • core/components/minishop3/src/Processors/Product/Create.php
  • core/components/minishop3/src/Processors/Product/Update.php
  • core/components/minishop3/src/Model/msProduct.php (set()loadData())
  • core/components/minishop3/src/Utils/ExtraFields.php (loadMap())

Окружение

  • MiniShop3: актуальная dev / 3.x
  • MODX Revolution 3.x
  • PHP 8.2+
  • Extra field ext_id для класса MiniShop3\Model\msProductData

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels
    No fields configured for Feature.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions