Skip to content
  1. Компоненты
  2. MiniShop3
  3. Разработка
  4. Модели и схема БД

Модели и схема базы данных ​

Модели MiniShop3 — классы xPDO в namespace MiniShop3\Model.

Основные классы ​

php
use MiniShop3\Model\msProduct;
use MiniShop3\Model\msProductData;
use MiniShop3\Model\msOrder;
use MiniShop3\Model\msCustomer;

// Получение товара
$product = $modx->getObject(msProduct::class, $id);

// Получение данных товара
$data = $product->getOne('Data');

// Получение заказа с адресом
$order = $modx->getObject(msOrder::class, $id);
$address = $order->getOne('Address');

// Получение покупателя
$customer = $modx->getObject(msCustomer::class, ['email' => $email]);

Связи между моделями ​

getOne() — для 1:1 и N:1, getMany() — для 1:N.

МодельАлиасТипВозвращает
msProductData1:1данные товара (msProductData)
msProductOptions1:Nзначения опций
msProductDataFiles1:Nфайлы галереи
msOrderAddress1:1адрес заказа (msOrderAddress)
msOrderProducts1:Nтовары в заказе
msOrdermsCustomerN:1покупателя. Алиас именно такой, не Customer
msCustomerTokens1:Nтокены авторизации
msCustomerAddressCustomerN:1покупателя — обратная связь от адреса
msCustomerTokenCustomerN:1покупателя — обратная связь от токена

Связи Addresses у msCustomer нет: адреса читают прямым запросом к msCustomerAddress по customer_id — см. Работа с покупателем.

Файл схемы ​

core/components/minishop3/schema/minishop3.mysql.schema.xml
АтрибутЗначение
packageMiniShop3\Model
baseClassxPDO\Om\xPDOObject
platformmysql
version3.0

Таблицы базы данных ​

Товары ​

МодельТаблицаОписание
msProductsite_contentТовар (расширяет modResource)
msProductDatams3_productsДанные товара (цена, артикул, вес)
msProductFilems3_product_filesФайлы галереи товара
msProductOptionms3_product_optionsЗначения опций товара
msLinkms3_linksТипы связей товаров
msProductLinkms3_product_linksСвязи между товарами

msProductData — основные поля ​

ПолеТипОписание
idintID (совпадает с ID ресурса)
articlevarchar(50)Артикул
pricedecimal(12,2)Цена
old_pricedecimal(12,2)Старая цена
stockdecimal(13,3)Остаток на складе
weightdecimal(13,3)Вес
imagevarchar(255)Основное изображение
thumbvarchar(255)Превью изображения
preview_file_idintID файла галереи для главного превью (без смены порядка галереи)
vendor_idintID производителя
made_invarchar(100)Страна производства
newtinyint(1)Флаг «Новинка»
populartinyint(1)Флаг «Популярный»
favoritetinyint(1)Флаг «Избранное»
tagsjsonТеги
colorjsonЦвета
sizejsonРазмеры

Категории ​

МодельТаблицаОписание
msCategorysite_contentКатегория (расширяет modResource)
msCategoryMemberms3_product_categoriesСвязь товар-категория
msCategoryOptionms3_category_optionsОпции категории

Заказы ​

МодельТаблицаОписание
msOrderms3_ordersЗаказ
msOrderAddressms3_order_addressesАдрес доставки заказа
msOrderProductms3_order_productsТовары в заказе
msOrderLogms3_order_logsИстория изменений заказа
msOrderStatusms3_order_statusesСтатусы заказов

msOrder — основные поля ​

ПолеТипОписание
idintID заказа
user_idintID пользователя MODX
customer_idintID покупателя (msCustomer)
tokenvarchar(128)Токен сессии
uuidchar(36)UUID заказа
numvarchar(20)Номер заказа
costdecimal(12,2)Общая стоимость
cart_costdecimal(12,2)Стоимость товаров
delivery_costdecimal(12,2)Стоимость доставки
weightdecimal(13,3)Общий вес
status_idintID статуса
delivery_idintID способа доставки
payment_idintID способа оплаты
contextvarchar(100)Контекст MODX
order_commenttextКомментарий к заказу
idempotency_keyvarcharКлюч идемпотентности (программные заказы)
createdondatetimeДата создания
updatedondatetimeДата обновления

Покупатели ​

МодельТаблицаОписание
msCustomerms3_customersПокупатель магазина
msCustomerGroupms3_customer_groupsГруппы покупателей (сегментация, ACL каталога)
msCustomerAddressms3_customer_addressesСохранённые адреса покупателя
msCustomerTokenms3_customer_tokensТокены авторизации

msCustomerGroup — основные поля ​

ПолеТипОписание
idintID группы
namevarchar(191)Название группы
user_group_idintID группы пользователей MODX (принципал для modAccessResourceGroup)
activetinyint(1)Активна
created_atdatetimeДата создания
updated_atdatetimeДата обновления

msCustomer — основные поля ​

ПолеТипОписание
idintID покупателя
user_idintID связанного modUser (опционально)
first_namevarchar(191)Имя
last_namevarchar(191)Фамилия
phonevarchar(50)Телефон
emailvarchar(191)Email
passwordvarchar(255)Хеш пароля
is_activetinyint(1)Активен
is_blockedtinyint(1)Заблокирован
email_verified_atdatetimeДата подтверждения email
orders_countintКоличество заказов
total_spentdecimal(12,2)Сумма всех заказов
last_order_atdatetimeДата последнего заказа
privacy_accepted_atdatetimeДата принятия политики

Отличие от miniShop2

В miniShop2 покупатель — это modUser. В MiniShop3 покупатель — отдельная сущность msCustomer, связь с modUser необязательна.

Доставка и оплата ​

МодельТаблицаОписание
msDeliveryms3_deliveriesСпособы доставки
msPaymentms3_paymentsСпособы оплаты
msDeliveryMemberms3_delivery_paymentsСвязь доставка-оплата

Отгрузки ​

Таблицы без xPDO-моделей: доступ через PdoShipmentStore и DI-ключ ms3_shipment_lifecycle (ShipmentLifecycleService). Миграции Phinx: create_shipments, create_shipment_events.

ТаблицаОписание
ms3_shipmentsОдна отгрузка на заказ (uniq_shipment_order)
ms3_shipment_eventsИдемпотентность webhook: provider_event_id на отгрузку

ms3_shipments — поля ​

ПолеТипОписание
idintID отгрузки
order_idint unsignedID заказа (unique)
delivery_idint unsignedID способа доставки
statusvarchar(32)preparing, shipped, in_transit, delivered, cancelled, returned, failed
tracking_numbervarchar(191)Трек-номер
external_idvarchar(191)ID у провайдера
providervarchar(191)Класс/имя провайдера
carriervarchar(191)Перевозчик
shipped_atint unsignedUnix-время отправки
delivered_atint unsignedUnix-время доставки
last_event_idvarchar(191)Последний provider_event_id
metatextJSON-метаданные
createdon / updatedonint unsignedUnix-время

При ms3_shipment_enabled=1 статус отгрузки переносит в заказ OrderStatusService: shipped → ms3_status_sent, cancelled/failed → ms3_status_canceled. Для delivered / in_transit смотрите настройки ms3_shipment_on_*_status.

Производители ​

МодельТаблицаОписание
msVendorms3_vendorsПроизводители товаров

Опции ​

МодельТаблицаОписание
msOptionms3_optionsСправочник опций
msOptionGroupms3_option_groupsГруппы опций (заменили использование modCategory для группировки, начиная с 1.11.0)

Конфигурация полей ​

МодельТаблицаОписание
msModelFieldms3_model_fieldsНастройки полей моделей
msModelFieldSectionms3_model_field_sectionsСекции полей
msGridFieldms3_grid_fieldsКонфигурация колонок гридов (customers, orders, products)
msProductFieldms3_product_fieldsПоля товара (устаревшие)
msPageSectionms3_page_sectionsСекции страниц (устаревшие)
msExtraFieldms3_extra_fieldsДополнительные поля

Уведомления ​

МодельТаблицаОписание
msNotificationConfigms3_notification_configsКонфигурация уведомлений

Примеры работы с моделями ​

Создание заказа ​

Так заказ создавать не стоит

Пример ниже показывает, как устроены записи заказа в базе, и только. Собранный так заказ не прошёл валидацию, не получил расчёт стоимости и доставки, не поднял события и не отправил уведомления.

Для создания заказов из кода есть сервис ms3_programmatic_order (ProgrammaticOrderService): он строит черновик и проводит его через обычное оформление, а по idempotency_key не создаёт дубль при повторном вызове.

php
$result = $modx->services->get('ms3_programmatic_order')->create([
    'idempotency_key' => 'crm-order-15', // обязателен
    'products' => [
        ['id' => $productId, 'count' => 2],
    ],
    'delivery_id' => $deliveryId,
    'payment_id' => $paymentId,
]);
php
use MiniShop3\Model\msOrder;
use MiniShop3\Model\msOrderProduct;

// Создание заказа
$order = $modx->newObject(msOrder::class);
$order->fromArray([
    'customer_id' => $customerId,
    'user_id' => $modx->user->get('id') ?: 0,
    'status_id' => $modx->getOption('ms3_status_new'),
    'delivery_id' => $deliveryId,
    'payment_id' => $paymentId,
    'context' => $modx->context->key,
]);
$order->save();

// Добавление товара в заказ
$orderProduct = $modx->newObject(msOrderProduct::class);
$orderProduct->fromArray([
    'order_id' => $order->get('id'),
    'product_id' => $productId,
    'name' => $productName,
    'price' => $price,
    'count' => $count,
    'cost' => $price * $count,
    'options' => json_encode($options),
]);
$orderProduct->save();

Работа с покупателем ​

php
use MiniShop3\Model\msCustomer;
use MiniShop3\Model\msCustomerAddress;

// Поиск или создание покупателя
$customer = $modx->getObject(msCustomer::class, ['email' => $email]);
if (!$customer) {
    $customer = $modx->newObject(msCustomer::class);
    $customer->fromArray([
        'email' => $email,
        'first_name' => $firstName,
        'phone' => $phone,
    ]);
    $customer->save();
}

// Добавление адреса
$address = $modx->newObject(msCustomerAddress::class);
$address->fromArray([
    'customer_id' => $customer->get('id'),
    'city' => $city,
    'street' => $street,
    'building' => $building,
]);
$address->save();

// Получение всех адресов покупателя — прямым запросом:
// связи Addresses у msCustomer нет
$addresses = $modx->getIterator(msCustomerAddress::class, [
    'customer_id' => $customer->get('id'),
]);

Итерация по записям ​

getIterator() достаёт записи по одной и не держит всю выборку в памяти — на больших наборах это предпочтительнее getCollection():

php
use MiniShop3\Model\msOrder;

foreach ($modx->getIterator(msOrder::class, ['status_id' => 2]) as $order) {
    // Обработка заказа
}

getIterator() и классы без своей таблицы

getCollection() и getObject() сами добавляют в условие фильтр по class_key — это делает addDerivativeCriteria(). Более того, msProduct и msCategory дублируют эту защиту в своих loadCollection() и load(). А getIterator() не проходит ни через один из этих методов: он создаёт итератор напрямую, и фильтра не будет.

Для msProduct и msCategory это критично: своей таблицы у них нет, они живут в site_content вместе со всеми ресурсами MODX. Без фильтра выборка захватит чужие записи, и первая же из них даст ошибку вида «Instantiated a derived class msProduct that is not a subclass of the requested class msCategory».

Поэтому при getIterator() для таких классов class_key задавайте вручную:

php
$criteria = [
    'class_key' => msProduct::class, // обязательно
    'parent' => $categoryId,
];

foreach ($modx->getIterator(msProduct::class, $criteria) as $product) {
    // …
}

Моделей со своей таблицей — msOrder, msCustomer, msProductData и остальных из таблиц выше — это не касается.

Архитектура моделей ​

Модель отвечает за таблицу и связи, бизнес-логика живёт в сервисах — см. Сервисный слой.

Полностью «тонкими» модели не стали: msProduct и msCategory расширяют modResource, а MODX держит бизнес-логику в моделях. Поэтому модель вправе вызвать сервис:

php
// src/Model/msProductData.php
class msProductData extends xPDOSimpleObject
{
    public function getPrice(array $data = []): float
    {
        // Делегирование в сервис форматирования
        $ms3 = $this->xpdo->services->get('ms3');
        return $ms3->format->price($this->get('price'));
    }
}

Регенерация моделей ​

Модели создаются вручную

В MiniShop3 модели НЕ генерируются автоматически из XML-схемы через buildModel(). Они создаются и поддерживаются вручную в директории src/Model/.

Это позволяет:

  • Добавлять свои методы в модели
  • Использовать полную типизацию PHP 8
  • Иметь полный контроль над кодом моделей

Важно: Не запускайте buildModel() — это перезапишет методы, добавленные вручную.

Структура файлов модели ​

src/Model/
├── msProduct.php           # Основной класс модели
├── mysql/
│   └── msProduct.php       # MySQL-специфичный маппинг

Пример модели ​

php
<?php
namespace MiniShop3\Model;

use MODX\Revolution\modResource;

class msProduct extends modResource
{
    // Свои методы модели
    public function getPrice(): float
    {
        $data = $this->getOne('Data');
        return $data ? (float)$data->get('price') : 0.0;
    }
}

Пример маппинга ​

php
<?php
namespace MiniShop3\Model\mysql;

class msProduct extends \MiniShop3\Model\msProduct
{
    public static $metaMap = [
        'package' => 'MiniShop3\\Model\\',
        'version' => '3.0',
        'extends' => 'MODX\\Revolution\\modResource',
        'fields' => [
            'class_key' => 'MiniShop3\\Model\\msProduct',
        ],
        // ... остальные метаданные
    ];
}

Миграции Phinx ​

Схему базы данных меняют миграции Phinx:

core/components/minishop3/migrations/
├── 20251020000000_initial_schema.php
├── 20251021000000_seed_page_sections.php
├── 20251023000000_seed_order_statuses.php
└── ...

Запуск миграций ​

Руками запускать их обычно не нужно: при установке и обновлении пакета миграции применяет резолвер resolver_02_migrations, а при работе с дополнительными полями — ExtraFieldsService. Оба используют окружение production, оно же стоит в phinx.php по умолчанию.

Для разработки миграции прогоняют из CLI:

bash
cd /path/to/modx
php core/components/minishop3/vendor/bin/phinx migrate -c core/components/minishop3/phinx.php

Пример миграции ​

php
<?php
use Phinx\Migration\AbstractMigration;

class CreateCustomersTable extends AbstractMigration
{
    public function change()
    {
        $table = $this->table('ms3_customers', [
            'engine' => 'InnoDB',
            'collation' => 'utf8mb4_unicode_ci',
        ]);

        $table
            ->addColumn('email', 'string', ['limit' => 191])
            ->addColumn('first_name', 'string', ['limit' => 191, 'null' => true])
            ->addColumn('phone', 'string', ['limit' => 50, 'null' => true])
            ->addIndex(['email'], ['unique' => true])
            ->create();
    }
}

Диаграмма связей ​

Товар ​

Заказ ​

ms3_shipments / ms3_shipment_events — PDO-таблицы (не классы MiniShop3\Model\*).

Линия msCustomer → msCustomerAddress показывает связь в базе: адреса ссылаются на покупателя через customer_id. Алиаса для обхода от покупателя к адресам в схеме нет, пройти можно только в обратную сторону — $address->getOne('Customer').