Описание функции
Добавить в MiniShop3 тип поля «повторитель» (repeater) — встроенный аналог MIGX для структурированных списков записей с фиксированным набором подполей.
Повторитель должен:
- редактироваться в менеджере как грид / таблица строк (добавить, удалить);
- поддерживать drag-and-drop сортировку строк — must-have для MVP (без этого повторитель теряет смысл по сравнению с textarea + JSON);
- храниться в БД в колонке
JSON (одна колонка на поле, массив объектов);
- при сохранении автоматически проставлять индекс/rank в каждой строке (как MIGX после перетаскивания) — чтобы порядок был явным в данных, а не только «по позиции в массиве»;
- быть доступен через существующую утилиту «Дополнительные поля» (
ms3_extra_fields) и формы товара/заказа, где уже подключаются extra fields;
- отдавать данные в API и сниппетах как массив структур в заданном порядке, а не как «сырой» JSON-строкой.
Пример доменной модели: у товара поле specifications — список { "name": "...", "value": "...", "rank": 0 }, у заказа — список позиций доп. атрибутов и т.п.
См. также: #300 — тип поля key=value (ms3-key-value) — UI «ключ → значение» для плоского ассоциативного массива (JSON object), без табличной схемы колонок. Проще repeater'а; удобен для КБЖУ, произвольных meta-пар и настроек.
Проблема, которую решает
Сейчас для повторяющихся структур в MiniShop3 остаются обходные пути:
- MODX TV + MIGX / MIGXdb — отдельная экосистема, не интегрированная с extra fields, гридами каталога и REST API компонента «из коробки»; но у MIGX есть ожидаемый UX: drag-and-drop + авто-rank при save — это нужно повторить.
- Плоское extra field
dbtype=json — колонку можно создать в утилите дополнительных полей, но нет UI повторителя: редактор не знает схему подполей, нет валидации строк, нет сортировки строк, нет удобного грида в карточке товара.
- Встроенные JSON-массивы (
tags, color, size у msProductData) — это простые списки значений, а не произвольная табличная структура с разными типами колонок.
- TV + join — для выборки и сортировки по вложенным данным нужны тяжёлые join к
modx_site_tmplvar_*; при хранении в JSON-колонке модели MS3 (MySQL 5.7+ / MariaDB 10.3+) доступны фильтрация и ORDER BY по JSON-path без TV.
В итоге для типовых кейсов («характеристики», «комплектация», «галерея метаданных», «несколько адресов доставки») приходится тянуть сторонние дополнения или писать кастомные формы/плагины.
Предлагаемое решение
1. Новый xtype / тип виджета: repeater (или ms3-repeater)
В ms3_extra_fields (и связанной Vue-админке) — отдельный тип поля поверх существующих xtype, dbtype, phptype:
| Уровень |
Значение |
Смысл |
dbtype |
json |
Колонка MySQL/MariaDB типа JSON; в БД хранится JSON-массив [{...}, {...}] |
phptype |
json |
Стандарт MS3/xPDO 3 для JSON-колонок: в PHP после загрузки — array, не строка (в ExtraFieldsManager подписано «json (массив)») |
xtype |
ms3-repeater (рабочее имя) |
Виджет грида строк в менеджере |
Контракт значения (важно для API и процессоров):
| Слой |
Тип |
Пример |
| БД |
JSON array |
[{"name":"Материал","value":"Хлопок","rank":0}, …] |
PHP / $product->get('specifications') |
array (индексированный список assoc-массивов) |
[['name'=>'…', 'value'=>'…', 'rank'=>0], …] |
| REST API / Vue |
array |
тот же список объектов, без JSON-строки |
Процессор Data |
array |
'specifications' => [['name'=>'…', 'value'=>'…'], …] — как в #297 |
Feedback @bs110060 (24.05.2026, phptype): «а массив не?» — да: в коде и API repeater — это массив. Отдельного phptype: array в extra fields MS3 сейчас нет; для JSON-колонок используется phptype: json, который в xPDO 3 автоматически decode/encode в PHP array (см. msProductData::getArraysValues(), лексикон ms3_vue_phptype_json = «json (массив)»). Для repeater в документации и контракте API явно писать array of rows, а phptype: json — техническая метадата колонки, как у tags / color / size.
Дополнительно — схема колонок повторителя (конфиг в ms3_extra_fields, отдельная колонка или JSON):
{
"columns": [
{ "key": "name", "label": "Название", "xtype": "textfield", "required": true },
{ "key": "value", "label": "Значение", "xtype": "textfield" }
],
"minRows": 0,
"maxRows": null,
"sortable": true,
"rankField": "rank"
}
Поле rankField (по умолчанию rank, аналог MIGX MIGX_id / rank) — служебное: не редактируется вручную в гриде, заполняется автоматически при save по текущему порядку строк после drag-and-drop (0, 1, 2… или 0, 10, 20… — на усмотрение реализации, главное — монотонный порядок).
Feedback @bs110060 (24.05.2026): сортировка строк — must-have. Отдельный индекс в схеме «на всякий случай» избыточен, если пользователь сам его заводит; но при наличии drag-and-drop индекс должен проставляться автоматически при сохранении — как в MIGX, этим активно пользуются в сниппетах и кастомной логике.
2. UI менеджера
- В ExtraFieldsManager — настройка схемы колонок при создании поля типа «повторитель»; флаг
sortable (по умолчанию true).
- В карточке товара / заказа — PrimeVue
DataTable или аналог: строки, inline-edit, drag-and-drop сортировка строк (обязательно в MVP), кнопки «Добавить / Удалить».
- Порядок строк при save (двойной контракт, как у MIGX):
- Порядок элементов в JSON-массиве = порядок после drag-and-drop в редакторе (основной источник истины для
{foreach} / API).
- Авто-заполнение
rankField в каждой строке: rank: 0, rank: 1, … по позиции после DnD — чтобы плагины/шаблоны могли сортировать явно (usort по rank, Fenom @sort:), как привыкли пользователи MIGX.
- При добавлении новой строки — в конец списка; при save пересчитать rank у всех строк.
- При удалении строки — пересчитать rank у оставшихся.
- Сохранение — сериализация в JSON-массив объектов в колонку модели (
msProductData, msOrder, …).
- При чтении API/формы — массив в порядке JSON-массива; дополнительно можно отдавать/нормализовать по
rankField ASC, если порядок в payload нарушен.
Не в MVP: сортировка по колонке кликом в заголовке таблицы (UI table sort) — только DnD.
3. Бэкенд
- Валидация payload по схеме (Rakit / существующие паттерны MS3).
- Нормализация при save: пересчёт
rankField по порядку массива; strip/reject дубликатов rank при необходимости.
- Нормализация JSON (как для
phptype: json → PHP array в ProductDataService::prepareObject).
- При save: принимать PHP-массив (или JSON-массив в HTTP body — decode до array); отклонять строку с «сырым» JSON, если передана без decode.
loadMap() / Object Extension — без изменения контракта: поле остаётся одной JSON-колонкой.
- API менеджера и публичный слой — отдавать декодированный массив, не строку.
4. Выборка, фильтрация и сортировка (MySQL 5.7+ / MariaDB 10.3+)
Исходная идея автора (@rasxod): хранить структурированные данные в JSON-колонке модели MS3, а не в TV — тогда поиск, фильтрация и сортировка по вложенным полям делаются через JSON-функции SQL без join к modx_site_tmplvar_* / MIGXdb. Это особенно полезно для гридов каталога, REST-фильтров и отчётов.
Фильтрация
-- товары, у которых в specifications есть name = 'Материал'
JSON_CONTAINS(
ms3_products.specifications,
JSON_OBJECT('name', 'Материал'),
'$'
)
Сортировка каталога по значению внутри JSON
-- по полю внутри массива объектов (path к конкретному ключу)
ORDER BY JSON_UNQUOTE(JSON_EXTRACT(ms3_products.specifications, '$[0].value')) ASC
Сортировка строк внутри одного товара (repeater)
- В UI: drag-and-drop → порядок массива + авто-
rank при save.
- В API / Fenom: итерация по массиву в сохранённом порядке; при необходимости сортировка по
rank / rankField до вывода (совместимость с MIGX-мышлением).
- Через API save: если клиент передал массив без
rank — бэкенд проставляет rank по порядку элементов.
Manager API / pdoTools (follow-up)
- параметры вида
sortBy=specifications.0.value / filter[specifications.name]=Материал с маппингом на безопасные JSON-path (whitelist из схемы extra field);
- generated column + index на часто используемый path — если понадобится производительность на больших каталогах.
На этапе MVP достаточно фильтрации + документированных SQL-примеров; полноценный sort/filter в Grid API — follow-up.
5. Область первой итерации (MVP)
- Extra fields для классов
msProductData, msOrder, msOrderAddress (по аналогии с текущими extra fields).
- Редактирование в Vue-менеджере.
- Drag-and-drop сортировка строк + авто-
rankField при save (MIGX-паттерн).
- Чтение в API / Fenom / pdoTools.
- Документация + пример схемы «характеристики товара».
Вне MVP (follow-up): импорт CSV, сортировка/фильтры грида каталога по JSON-path в Manager API, nested repeaters, версионирование схемы, ручное редактирование rank в UI. Смежный тип key=value — отдельная задача #300.
Альтернативы
| Вариант |
Плюсы |
Минусы |
| MIGX / MIGXdb (TV) |
Зрелый UI, DnD + rank, привычен сообществу MODX |
Вне MS3, дублирование данных, слабая связь с extra fields и Manager API |
Плоский json extra field + textarea |
Уже можно создать колонку |
Нет UX, нет сортировки, легко сломать JSON |
| Key=value (#300) |
Проще для flat map (КБЖУ, meta) |
Не подходит для однотипных строк с несколькими колонками |
| Только порядок массива, без rank |
Меньше полей в JSON |
Ломает ожидания пользователей MIGX; сложнее отладка и кастомная сортировка |
| Rank только вручную в схеме |
Гибко |
Без DnD + auto-rank — лишняя работа редактору; feedback: излишне |
Отдельная таблица ms3_repeater_rows |
Нормализованная модель, простые SQL-join |
Сложнее миграции и API; избыточно для MVP |
| MODX Collections / custom tables |
Гибко для больших объёмов |
Вне компонента, выше порог входа |
Предлагаемый баланс: JSON-колонка + схема в extra field + UI повторителя с DnD и авто-rank при save — минимальные изменения схемы БД при сохранении удобства MIGX-подобного редактора.
Примеры использования
Характеристики товара (после save с DnD)
[
{ "name": "Материал", "value": "Хлопок 100%", "rank": 0 },
{ "name": "Страна", "value": "Россия", "rank": 1 }
]
Порядок на витрине = порядок в массиве (= rank ASC после нормализации на бэкенде).
Комплектация (несколько SKU в одной карточке)
[
{ "sku": "BOX-XL-01", "qty": 1, "label": "Коробка XL", "rank": 0 },
{ "sku": "RIBBON-RED", "qty": 2, "label": "Лента", "rank": 1 }
]
Передача через процессор / API (после #297)
$response = $modx->runProcessor('MiniShop3\\Processors\\Product\\Create', [
'pagetitle' => 'Бокс «Весенний каприз» XL',
'class_key' => \MiniShop3\Model\msProduct::class,
'parent' => 409,
'Data' => [
'price' => 5200,
'specifications' => [
['name' => 'Объём', 'value' => 'XL'],
['name' => 'Наполнение', 'value' => 'Ассорти'],
],
],
]);
// бэкенд при save проставит rank: 0, 1 по порядку массива
Fenom / витрина
{* порядок массива уже после DnD + save *}
{foreach $specifications as $row}
<dt>{$row.name}</dt><dd>{$row.value}</dd>
{/foreach}
{* или явно по rank, как в MIGX-сниппетах *}
{foreach $specifications|sort:'rank':'asc' as $row}
...
{/foreach}
Дополнительный контекст
- MySQL JSON (5.7+) и MariaDB JSON (10.3+) поддерживают поиск, фильтрацию и сортировку по JSON-path — ключевое преимущество перед TV+MIGX: одна колонка в
ms3_products / ms3_orders вместо join к таблицам TV, при этом ORDER BY JSON_EXTRACT(...) и JSON_CONTAINS для выборок в каталоге и отчётах.
- В MS3 уже есть задел: утилита extra fields (
ExtraFields, ExtraFieldsManager.vue), типы dbtype=json / phptype=json, обработка JSON в ProductDataService и msProductData::getArraysValues().
- Аналоги: MIGX (MODX — DnD + rank при save), Filament Repeater (Laravel), repeater/meta fields в Shopify metafields, WooCommerce custom product attributes.
- Для плоских key-value пар см. #300 (Filament KeyValue, КБЖУ).
Связанные области кода (ориентиры для реализации):
core/components/minishop3/src/Utils/ExtraFields.php
vueManager/src/components/ExtraFieldsManager.vue
core/components/minishop3/src/Services/Product/ProductDataService.php
- миграции / модель
msExtraField
Критерии приёмки (черновик):
Описание функции
Добавить в MiniShop3 тип поля «повторитель» (repeater) — встроенный аналог MIGX для структурированных списков записей с фиксированным набором подполей.
Повторитель должен:
JSON(одна колонка на поле, массив объектов);ms3_extra_fields) и формы товара/заказа, где уже подключаются extra fields;Пример доменной модели: у товара поле
specifications— список{ "name": "...", "value": "...", "rank": 0 }, у заказа — список позиций доп. атрибутов и т.п.См. также: #300 — тип поля key=value (ms3-key-value) — UI «ключ → значение» для плоского ассоциативного массива (JSON object), без табличной схемы колонок. Проще repeater'а; удобен для КБЖУ, произвольных meta-пар и настроек.
Проблема, которую решает
Сейчас для повторяющихся структур в MiniShop3 остаются обходные пути:
dbtype=json— колонку можно создать в утилите дополнительных полей, но нет UI повторителя: редактор не знает схему подполей, нет валидации строк, нет сортировки строк, нет удобного грида в карточке товара.tags,color,sizeуmsProductData) — это простые списки значений, а не произвольная табличная структура с разными типами колонок.modx_site_tmplvar_*; при хранении в JSON-колонке модели MS3 (MySQL 5.7+ / MariaDB 10.3+) доступны фильтрация и ORDER BY по JSON-path без TV.В итоге для типовых кейсов («характеристики», «комплектация», «галерея метаданных», «несколько адресов доставки») приходится тянуть сторонние дополнения или писать кастомные формы/плагины.
Предлагаемое решение
1. Новый xtype / тип виджета:
repeater(илиms3-repeater)В
ms3_extra_fields(и связанной Vue-админке) — отдельный тип поля поверх существующихxtype,dbtype,phptype:dbtypejson[{...}, {...}]phptypejsonarray, не строка (в ExtraFieldsManager подписано «json (массив)»)xtypems3-repeater(рабочее имя)Контракт значения (важно для API и процессоров):
[{"name":"Материал","value":"Хлопок","rank":0}, …]$product->get('specifications')array(индексированный список assoc-массивов)[['name'=>'…', 'value'=>'…', 'rank'=>0], …]arrayDataarray'specifications' => [['name'=>'…', 'value'=>'…'], …]— как в #297Дополнительно — схема колонок повторителя (конфиг в
ms3_extra_fields, отдельная колонка или JSON):{ "columns": [ { "key": "name", "label": "Название", "xtype": "textfield", "required": true }, { "key": "value", "label": "Значение", "xtype": "textfield" } ], "minRows": 0, "maxRows": null, "sortable": true, "rankField": "rank" }Поле
rankField(по умолчаниюrank, аналог MIGXMIGX_id/ rank) — служебное: не редактируется вручную в гриде, заполняется автоматически при save по текущему порядку строк после drag-and-drop (0, 1, 2… или 0, 10, 20… — на усмотрение реализации, главное — монотонный порядок).2. UI менеджера
sortable(по умолчаниюtrue).DataTableили аналог: строки, inline-edit, drag-and-drop сортировка строк (обязательно в MVP), кнопки «Добавить / Удалить».{foreach}/ API).rankFieldв каждой строке:rank: 0,rank: 1, … по позиции после DnD — чтобы плагины/шаблоны могли сортировать явно (usortпоrank, Fenom@sort:), как привыкли пользователи MIGX.msProductData,msOrder, …).rankFieldASC, если порядок в payload нарушен.Не в MVP: сортировка по колонке кликом в заголовке таблицы (UI table sort) — только DnD.
3. Бэкенд
rankFieldпо порядку массива; strip/reject дубликатов rank при необходимости.phptype: json→ PHP array вProductDataService::prepareObject).loadMap()/ Object Extension — без изменения контракта: поле остаётся одной JSON-колонкой.4. Выборка, фильтрация и сортировка (MySQL 5.7+ / MariaDB 10.3+)
Исходная идея автора (@rasxod): хранить структурированные данные в JSON-колонке модели MS3, а не в TV — тогда поиск, фильтрация и сортировка по вложенным полям делаются через JSON-функции SQL без join к
modx_site_tmplvar_*/ MIGXdb. Это особенно полезно для гридов каталога, REST-фильтров и отчётов.Фильтрация
Сортировка каталога по значению внутри JSON
Сортировка строк внутри одного товара (repeater)
rankпри save.rank/rankFieldдо вывода (совместимость с MIGX-мышлением).rank— бэкенд проставляет rank по порядку элементов.Manager API / pdoTools (follow-up)
sortBy=specifications.0.value/filter[specifications.name]=Материалс маппингом на безопасные JSON-path (whitelist из схемы extra field);На этапе MVP достаточно фильтрации + документированных SQL-примеров; полноценный sort/filter в Grid API — follow-up.
5. Область первой итерации (MVP)
msProductData,msOrder,msOrderAddress(по аналогии с текущими extra fields).rankFieldпри save (MIGX-паттерн).Вне MVP (follow-up): импорт CSV, сортировка/фильтры грида каталога по JSON-path в Manager API, nested repeaters, версионирование схемы, ручное редактирование rank в UI. Смежный тип key=value — отдельная задача #300.
Альтернативы
jsonextra field + textareams3_repeater_rowsПредлагаемый баланс: JSON-колонка + схема в extra field + UI повторителя с DnD и авто-rank при save — минимальные изменения схемы БД при сохранении удобства MIGX-подобного редактора.
Примеры использования
Характеристики товара (после save с DnD)
[ { "name": "Материал", "value": "Хлопок 100%", "rank": 0 }, { "name": "Страна", "value": "Россия", "rank": 1 } ]Порядок на витрине = порядок в массиве (=
rankASC после нормализации на бэкенде).Комплектация (несколько SKU в одной карточке)
[ { "sku": "BOX-XL-01", "qty": 1, "label": "Коробка XL", "rank": 0 }, { "sku": "RIBBON-RED", "qty": 2, "label": "Лента", "rank": 1 } ]Передача через процессор / API (после #297)
Fenom / витрина
Дополнительный контекст
ms3_products/ms3_ordersвместо join к таблицам TV, при этомORDER BY JSON_EXTRACT(...)иJSON_CONTAINSдля выборок в каталоге и отчётах.ExtraFields,ExtraFieldsManager.vue), типыdbtype=json/phptype=json, обработка JSON вProductDataServiceиmsProductData::getArraysValues().Связанные области кода (ориентиры для реализации):
core/components/minishop3/src/Utils/ExtraFields.phpvueManager/src/components/ExtraFieldsManager.vuecore/components/minishop3/src/Services/Product/ProductDataService.phpmsExtraFieldКритерии приёмки (черновик):
sortable: true.rankField(по умолчаниюrank) в каждой строке по порядку после DnD — MIGX-паттерн.array), не JSON-строку; некорректный payload/схема отклоняются с понятной ошибкой.Data), вывод Fenom.ORDER BY JSON_EXTRACTв документации.