Описание проблемы
При программном создании товара через процессор 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 создано через утилиту дополнительных полей:
Фактическое поведение
price и другие встроенные колонки msProductData из статической xPDO-карты обычно сохраняются.
- Extra fields (
ext_id и аналоги из ms3_extra_fields / Object Extension) при том же плоском формате не записываются (или кажется, что «нет способа передать»).
Ожидаемое поведение
- Должен быть явный и документированный способ передать любые поля
msProductData — включая extra fields после loadMap().
- Контракт процессоров
Create / Update должен быть предсказуемым для интеграций (импорт, синхронизация с внешней системой, cron, Scheduler).
Анализ текущей реализации (код)
Как устроено сейчас
Processors\Product\Create и Update в beforeSet() обрабатывают только:
- поля
options-* → $ms3ProductFormOptions + saveOptions() в afterSave();
- нормализацию
vendor_id.
Отдельного разбора вложенного блока Data нет.
Почему «плоский» ext_id может не сработать
msProduct::set() перенаправляет ключи, которых нет в modResource, в msProductData — но xPDO принимает только поля, присутствующие в _fieldMeta модели.
- Extra fields попадают в карту динамически через
$ms3->loadMap() (OnMODXInit, плагин MiniShop3). Без актуальной карты set('ext_id', …) игнорируется.
- После создания поля в утилите может понадобиться сброс кэша extra fields (
ms3/extra_fields_meta) — иначе в другом контексте (CLI, фоновая задача) карта без ext_id.
- Процессор
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()):
- Если передан
Data и это массив — для каждого ключа вызвать $this->setProperty($key, $value) или сразу $this->object->set($key, $value) после инициализации объекта.
$this->unsetProperty('Data'), чтобы родительский процессор не получал «лишний» ключ.
- Перед применением —
$this->modx->services->get('ms3')->loadMap() (если ещё не загружено), чтобы extra fields были в _fieldMeta.
- Симметрично поддержать в
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', …) может сработать.
Критерии приёмки
Связанный код
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
Описание проблемы
При программном создании товара через процессор
MiniShop3\Processors\Product\Create(илиrunProcessorс тем же классом) значения Object Extension-полей таблицыms3_products(msProductData), созданных в утилите «Дополнительные поля» (напримерext_id), не попадают в запись — даже если стандартные поля вродеpriceпередаются в том же плоском$payload.Пример из интеграции:
Поле
ext_idсоздано через утилиту дополнительных полей:Фактическое поведение
priceи другие встроенные колонкиmsProductDataиз статической xPDO-карты обычно сохраняются.ext_idи аналоги изms3_extra_fields/ Object Extension) при том же плоском формате не записываются (или кажется, что «нет способа передать»).Ожидаемое поведение
msProductData— включая extra fields послеloadMap().Create/Updateдолжен быть предсказуемым для интеграций (импорт, синхронизация с внешней системой, cron, Scheduler).Анализ текущей реализации (код)
Как устроено сейчас
Processors\Product\CreateиUpdateвbeforeSet()обрабатывают только:options-*→$ms3ProductFormOptions+saveOptions()вafterSave();vendor_id.Отдельного разбора вложенного блока
Dataнет.Почему «плоский»
ext_idможет не сработатьmsProduct::set()перенаправляет ключи, которых нет вmodResource, вmsProductData— но xPDO принимает только поля, присутствующие в_fieldMetaмодели.$ms3->loadMap()(OnMODXInit, плагин MiniShop3). Без актуальной картыset('ext_id', …)игнорируется.ms3/extra_fields_meta) — иначе в другом контексте (CLI, фоновая задача) карта безext_id.MODX\Revolution\Processors\Resource\CreateприfromArray()может отбрасывать ключи, которых нет в_fieldMetaсамого ресурса (безignoreInvalid), не доходя до логикиmsProduct::set()для extra fields.Итого: для встроенных полей плоский payload часто «случайно работает», для Object Extension — ненадёжно и неочевидно.
Предложение: ключ
DataДля консистентности с join-алиасом
Dataв запросах и явного контракта API — поддержать вложенный массив:Черновик реализации
В
Create::beforeSet()/Update::beforeSet()(доparent::beforeSet()):Dataи это массив — для каждого ключа вызвать$this->setProperty($key, $value)или сразу$this->object->set($key, $value)после инициализации объекта.$this->unsetProperty('Data'), чтобы родительский процессор не получал «лишний» ключ.$this->modx->services->get('ms3')->loadMap()(если ещё не загружено), чтобы extra fields были в_fieldMeta.Update.Обратная совместимость
price,article, …) не ломать — оставить текущее поведение.Data— дополнительный, явный канал дляmsProductData, рекомендуемый для extra fields и интеграций.Workaround до фикса
Либо после
loadMap()попробовать плоский ключ в payload — если карта актуальна,msProduct::set('ext_id', …)может сработать.Критерии приёмки
Createсохраняет extra field (ext_id) при передаче черезData→['ext_id' => '…'].Update— то же.price/articleпо-прежнему работает.runProcessorдля товара с extra fields.Связанный код
core/components/minishop3/src/Processors/Product/Create.phpcore/components/minishop3/src/Processors/Product/Update.phpcore/components/minishop3/src/Model/msProduct.php(set()→loadData())core/components/minishop3/src/Utils/ExtraFields.php(loadMap())Окружение
ext_idдля классаMiniShop3\Model\msProductData