You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Связанные issues:#299 (repeater, закрыт), #297/#298 (ProductDataPayloadTrait), #301 (whitelist repeater в ProductDataService::updateProductData()).
Описание
Тип extra field ms3-key-value: UI для плоского ассоциативного массива (JSON object / map), пара «ключ → значение».
редактирование в менеджере: список пар «Параметр / Значение» (добавить, удалить);
хранение: одна JSON-колонка на поле (объект, не массив строк);
доступ через «Дополнительные поля» (ms3_extra_fields) и формы товара/заказа;
API и сниппеты отдают ассоциативный массив, не JSON-строку.
Проблема
Для flat map (КБЖУ, meta, flat config) repeater из #299 лишний: там схема колонок и JSON массив[{...}, {...}].
Сейчас обходные пути:
json + textarea — колонку создать можно (ExtraFieldsManager: dbtype=json, phptype=json), но нет UI пар, нет валидации ключей, легко сломать JSON.
MODX TV + произвольный JSON — вне extra fields MS3, слабая связь с Manager API.
ms3-repeater — UX тяжелее; модель [{name, value}], не {"calories": "250"}.
Ловушка текущего кода (важно для реализации)
msProductData::getArraysValues() отдаёт все поля с phptype=json в ProductDataService::prepareObject(). Repeater обрабатывается отдельно через RepeaterFieldService::processValue(). Остальные JSON-поля проходят prepareOptionValues() — логика для списков (tags, color, size): array_keys(array_flip(...))уничтожает ключи объекта.
WHERE JSON_EXTRACT(ms3_products.nutrition, '$.calories') IS NOT NULLORDER BY CAST(JSON_UNQUOTE(JSON_EXTRACT(ms3_products.nutrition, '$.calories')) AS UNSIGNED) ASC
Критерии приёмки
В Extra Fields можно создать поле ms3-key-value (fixed и free keys).
Phinx-миграция: key_value_config в ms3_extra_fields.
KeyValueFieldService: decode/normalize/validate; ошибки с lexicon.
prepareObject() не прогоняет key-value через prepareOptionValues().
Статус (актуализация кодовой базы, 2026-06-22)
ms3-repeater(#299, #301)ms3-key-value(#300)dbtype=json+textareaв Extra FieldsprepareObject()Связанные issues: #299 (repeater, закрыт), #297/#298 (
ProductDataPayloadTrait), #301 (whitelist repeater вProductDataService::updateProductData()).Описание
Тип extra field
ms3-key-value: UI для плоского ассоциативного массива (JSON object / map), пара «ключ → значение».ms3_extra_fields) и формы товара/заказа;Проблема
Для flat map (КБЖУ, meta, flat config) repeater из #299 лишний: там схема колонок и JSON массив
[{...}, {...}].Сейчас обходные пути:
json+textarea— колонку создать можно (ExtraFieldsManager:dbtype=json,phptype=json), но нет UI пар, нет валидации ключей, легко сломать JSON.ms3-repeater— UX тяжелее; модель[{name, value}], не{"calories": "250"}.Ловушка текущего кода (важно для реализации)
msProductData::getArraysValues()отдаёт все поля сphptype=jsonвProductDataService::prepareObject(). Repeater обрабатывается отдельно черезRepeaterFieldService::processValue(). Остальные JSON-поля проходятprepareOptionValues()— логика для списков (tags,color,size):array_keys(array_flip(...))уничтожает ключи объекта.Вывод: key-value нельзя выпускать как «просто json + textarea». Нужен отдельный xtype и ветка нормализации, как у repeater.
Сравнение с repeater (#299)
ms3-repeater)ms3-key-value)ms3_extra_fieldsrepeater_configkey_value_config(новая колонка)Repeater уже задаёт паттерн полного стека — #300 повторяет его, не изобретая новую архитектуру.
Предлагаемое решение
1. Контракт типа
dbtypejsonphptypejsonxtypems3-key-valuekey_value_config(JSON вms3_extra_fields, аналогrepeater_config):{ "mode": "fixed", "keys": [ { "key": "calories", "label": "Калории", "valueType": "string" }, { "key": "protein", "label": "Белки", "valueType": "string" } ] }mode: "free"— произвольные ключи (Filament KeyValue). MVP: оба режима.2. Бэкенд (следовать паттерну
RepeaterFieldService)Новый класс
MiniShop3\Services\ExtraFields\KeyValueFieldService:XTYPE = 'ms3-key-value';parseConfig/validateConfigSchema/encodeConfig;decodeValue→ ассоциативный массив (отклонять JSON-массив);normalizeMap— trim, удаление пар с пустым ключом;validateMap— уникальность ключей, обязательные ключи вfixed, numeric check поvalueType;processValue— decode → normalize → validate;getKeyValueFieldsForClass(string $modelClass)— кэш по классу.Интеграция (те же точки, что у repeater):
ServiceRegistry.phpExtraFieldsService.phpmsProductFieldconfig, очисткаkey_value_configпри смене xtypeProductDataService.phpprepareObject(); whitelist +normalizeKeyValueFieldsInPayload()вupdateProductData()(урок #301)OrdersController.phpProductDataPayloadTrait.phpkey_value_config TEXT NULLвms3_extra_fieldsmsExtraFieldmysql model + schema XMLkey_value_config3. Фронт (Vue Manager)
vueManager/src/utils/keyValueField.jsvueManager/src/components/KeyValueField.vuevueManager/src/components/KeyValueSchemaEditor.vueExtraFieldsManager.vuedbtype=json, schema editorDynamicField.vuems3-key-value+ hidden input для legacy MODX form (как repeater, #298/#301)OrderExtraFieldsSection.vuecol-12для key-value (как repeater)4. Code-judo (structural)
Не плодить
if (xtype === …)в десяти файлах без модели:KeyValueFieldServiceзеркалитRepeaterFieldService— предсказуемо для контрибьюторов MS3.StructuredJsonFieldServiceTraitдляparseConfig/encodeConfig/malformed JSON log — только если после [Feature] Тип поля key=value (ms3-key-value) для extra fields #300 появится третий structured JSON xtype.MVP
msProductData,msOrder,msOrderAddress(заказы уже черезOrderExtraFieldsSection).Вне MVP: i18n labels из схемы на витрине, generated column + index на JSON path, sort/filter в Grid API.
Примеры
КБЖУ (
nutrition){ "calories": "250", "protein": "12", "fat": "8", "carbs": "30" }Save через processor (#298)
Fenom
SQL (MySQL 5.7+ / MariaDB 10.3+)
Критерии приёмки
ms3-key-value(fixed и free keys).key_value_configвms3_extra_fields.KeyValueFieldService: decode/normalize/validate; ошибки с lexicon.prepareObject()не прогоняет key-value черезprepareOptionValues().ProductDataService::updateProductData()включает key-value keys в whitelist (regression guard как feat(extra-fields): repeater field type (ms3-repeater) #301).DynamicField.vue: hidden input для legacy form POST.phptype=json).Datasave, Fenom,JSON_EXTRACT.Эталон в коде (repeater, уже merged)
Альтернативы
prepareObject()ломает object