Skip to content
MiniShop3
MiniShop3
Современный компонент интернет-магазина для MODX 3
  1. Компоненты
  2. MiniShop3
  3. Разработка
  4. События
  5. msProducts

События сниппета msProducts

События для интеграции внешних пакетов (ms3Variants, msBrands и др.) в сниппет msProducts без модификации кода ядра MiniShop3.

Параметр usePackages

Для активации загрузки данных внешнего пакета укажите его имя в параметре сниппета:

fenom
{'msProducts' | snippet : [
    'parents' => 0,
    'usePackages' => 'ms3Variants,msBrands'
]}

Плагины проверяют наличие своего пакета в этом параметре и загружают данные только при необходимости.

msOnProductsLoad

Вызывается после загрузки списка товаров из БД, до их обработки. Предназначен для bulk-загрузки дополнительных данных одним запросом — это позволяет избежать проблемы N+1.

Параметры

ПараметрТипОписание
rowsarrayМассив товаров. Изменения применяются только через returnedValues['rows'] — PHP-ссылка на этом уровне не сохраняется (см. ниже)
productIdsarrayМассив ID товаров [1, 2, 3, ...]
usePackagesarrayСписок запрошенных пакетов ['ms3Variants', 'msBrands']
scriptPropertiesarrayВсе параметры вызова сниппета

После события строки списка обновляются из returnedValues['rows'] (EventGate::applyReturnedArray). List в rows заменяет весь массив товаров, assoc-пatch отдельной строки — через msOnProductPrepare.

returnedValues

php
<?php
switch ($modx->event->name) {
    case 'msOnProductsLoad':
        $values = &$modx->event->returnedValues;
        // Пример: пометить все товары без цены
        $rows = $scriptProperties['rows'];
        foreach ($rows as $i => $row) {
            if (empty($row['price'])) {
                $rows[$i]['price_hidden'] = true;
            }
        }
        $values['rows'] = $rows;
        break;
}

Базовый пример

php
<?php
switch ($modx->event->name) {
    case 'msOnProductsLoad':
        // Проверяем, запрошен ли наш пакет
        $usePackages = $scriptProperties['usePackages'] ?? [];
        if (!in_array('myPackage', $usePackages)) {
            return;
        }

        // Загружаем данные для ВСЕХ товаров одним запросом
        $productIds = $scriptProperties['productIds'];

        // Ваша логика загрузки данных
        $myData = loadDataForProducts($productIds);

        // Сохраняем в eventData для использования в msOnProductPrepare
        $modx->eventData['myPackage'] = [
            'dataMap' => $myData,
        ];
        break;
}

Пример: загрузка вариантов товаров (ms3Variants)

php
<?php
switch ($modx->event->name) {
    case 'msOnProductsLoad':
        // Проверяем, запрошен ли наш пакет
        $usePackages = $scriptProperties['usePackages'] ?? [];
        if (!in_array('ms3Variants', $usePackages)) {
            return;
        }

        // Проверяем, установлен ли пакет
        if (!$modx->services->has('ms3variants')) {
            return;
        }

        // Загружаем варианты для ВСЕХ товаров одним запросом
        $productIds = $scriptProperties['productIds'];
        $variantService = $modx->services->get('ms3variants_variant_service');

        // Сохраняем в eventData для msOnProductPrepare
        $modx->eventData['ms3variants'] = [
            'variantsMap' => $variantService->getVariantsForProducts($productIds, ['active' => true]),
        ];
        break;
}

msOnProductPrepare

Вызывается при подготовке каждого товара к рендерингу. Используется для присоединения данных, загруженных в msOnProductsLoad, к конкретному товару.

Параметры

ПараметрТипОписание
rowarrayДанные товара. Изменения применяются только через returnedValues['row'] — PHP-ссылка на этом уровне не сохраняется (см. ниже)
productIdintID товара
idxintПорядковый номер товара в выборке

После события строка обновляется из returnedValues['row'] (patch или list-замена, как в import).

php
<?php
switch ($modx->event->name) {
    case 'msOnProductPrepare':
        $modx->event->returnedValues = [
            'row' => ['badge' => 'sale'],
        ];
        break;
}

Мутация $scriptProperties['row'] по ссылке не работает

Хотя row помечен как «ссылка» на стороне вызова (EventGate::invokeRaw(..., ['row' => &$rows[$k], ...])), PHP-ссылка не переживает merge-конвейер MODX-ядра (modElement::getProperties()modX::invokeEvent(), оба используют array_merge(), который разыменовывает ссылки). Прямое изменение $scriptProperties['row']['field'] = ... внутри плагина не доходит до шаблона — нужно обязательно выставлять $modx->event->returnedValues['row'], как показано ниже.

Базовый пример

php
<?php
switch ($modx->event->name) {
    case 'msOnProductPrepare':
        // Если данных нет — пакет не был запрошен или не установлен
        $myData = $modx->eventData['myPackage']['dataMap'] ?? null;
        if ($myData === null) {
            return;
        }

        $productId = $scriptProperties['productId'];

        // Присоединяем данные к товару — только через returnedValues,
        // прямая мутация $scriptProperties['row'] по ссылке до шаблона не доходит
        if (isset($myData[$productId])) {
            $modx->event->returnedValues = [
                'row' => ['my_field' => $myData[$productId]],
            ];
        }
        break;
}

Пример: присоединение вариантов (ms3Variants)

php
<?php
switch ($modx->event->name) {
    case 'msOnProductPrepare':
        // Если данных нет — пакет не был запрошен
        $variantsMap = $modx->eventData['ms3variants']['variantsMap'] ?? null;
        if ($variantsMap === null) {
            return;
        }

        $productId = $scriptProperties['productId'];

        // Присоединяем варианты — только через returnedValues (см. предупреждение выше)
        if (isset($variantsMap[$productId])) {
            $row = [
                'variants' => $variantsMap[$productId],
                'variants_count' => count($variantsMap[$productId]),
                'variants_json' => json_encode($variantsMap[$productId]),
                'has_variants' => true,
            ];
        } else {
            $row = [
                'variants' => [],
                'variants_count' => 0,
                'variants_json' => '[]',
                'has_variants' => false,
            ];
        }

        $modx->event->returnedValues = ['row' => $row];
        break;
}

Полный пример плагина интеграции

Пример плагина, добавляющего бейджи "Новинка" к товарам, созданным за последние 7 дней.

php
<?php
/**
 * Плагин: Бейджи товаров
 * События: msOnProductsLoad, msOnProductPrepare
 */

switch ($modx->event->name) {

    case 'msOnProductsLoad':
        // Проверяем параметр
        $usePackages = $scriptProperties['usePackages'] ?? [];
        if (!in_array('msBadges', $usePackages)) {
            return;
        }

        $rows = $scriptProperties['rows'];
        $weekAgo = strtotime('-7 days');

        // Определяем новинки (созданы за последние 7 дней)
        $newProducts = [];
        foreach ($rows as $row) {
            if (strtotime($row['createdon']) > $weekAgo) {
                $newProducts[$row['id']] = true;
            }
        }

        // Сохраняем для msOnProductPrepare
        $modx->eventData['msBadges'] = [
            'newProducts' => $newProducts,
        ];
        break;

    case 'msOnProductPrepare':
        $badgesData = $modx->eventData['msBadges'] ?? null;
        if ($badgesData === null) {
            return;
        }

        $productId = $scriptProperties['productId'];
        $row = $scriptProperties['row']; // читаем, не мутируем — для записи нужен returnedValues

        $badges = [];

        // Бейдж "Новинка"
        if (!empty($badgesData['newProducts'][$productId])) {
            $badges[] = [
                'type' => 'new',
                'label' => 'Новинка',
                'color' => '#28a745',
            ];
        }

        // Бейдж "Скидка" (если есть старая цена)
        if (!empty($row['old_price']) && $row['old_price'] > $row['price']) {
            $discount = round((($row['old_price'] - $row['price']) / $row['old_price']) * 100);
            $badges[] = [
                'type' => 'sale',
                'label' => "-{$discount}%",
                'color' => '#dc3545',
            ];
        }

        $modx->event->returnedValues = [
            'row' => [
                'badges' => $badges,
                'badges_json' => json_encode($badges),
                'has_badges' => !empty($badges),
            ],
        ];
        break;
}

Использование в шаблоне

fenom
{'msProducts' | snippet : [
    'parents' => 0,
    'usePackages' => 'msBadges',
    'tpl' => 'tpl.msProducts.badges'
]}

Чанк tpl.msProducts.badges:

fenom
<div class="product-card">
    {if $has_badges}
        <div class="product-badges">
            {foreach $badges as $badge}
                <span class="badge" style="background: {$badge.color}">
                    {$badge.label}
                </span>
            {/foreach}
        </div>
    {/if}

    <h3>{$pagetitle}</h3>
    <div class="price">{$price} руб.</div>
</div>

Передача данных между событиями

Используйте $modx->eventData для передачи данных между событиями:

php
// В msOnProductsLoad — сохраняем
$modx->eventData['myPackage'] = [
    'dataMap' => $loadedData,
    'settings' => $mySettings,
];

// В msOnProductPrepare — читаем
$dataMap = $modx->eventData['myPackage']['dataMap'] ?? null;
$settings = $modx->eventData['myPackage']['settings'] ?? [];

Изоляция данных

Используйте имя вашего пакета как ключ в eventData, чтобы избежать конфликтов с другими плагинами.


Производительность

События спроектированы для оптимальной производительности:

  1. msOnProductsLoad — вызывается один раз для загрузки данных всех товаров
  2. msOnProductPrepare — вызывается для каждого товара, но только присоединяет уже загруженные данные

Это позволяет избежать проблемы N+1 запросов:


❌ Без bulk-загрузки: 1 запрос на список + N запросов на варианты = O(N+1)
✅ С bulk-загрузкой:  1 запрос на список + 1 запрос на все варианты = O(2)