Skip to content
  1. Extras
  2. MiniShop3
  3. Development
  4. Models and database schema

Models and database schema ​

MiniShop3 uses xPDO ORM for database access. All models live in the MiniShop3\Model namespace.

xPDO models ​

Core classes ​

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

// Get product
$product = $modx->getObject(msProduct::class, $id);

// Get product data
$data = $product->getOne('Data');

// Get order with address
$order = $modx->getObject(msOrder::class, $id);
$address = $order->getOne('Address');

// Get customer
$customer = $modx->getObject(msCustomer::class, ['email' => $email]);

Model relations ​

php
// Product → Product data (1:1)
$product->getOne('Data');

// Product → Options (1:N)
$product->getMany('Options');

// Product → Gallery files (1:N)
$data->getMany('Files');

// Order → Order products (1:N)
$order->getMany('Products');

// Order → Customer (N:1)
$order->getOne('msCustomer');

// Customer → Addresses (1:N)
$customer->getMany('Addresses');

Schema file ​

The database schema is defined in an XML file:

core/components/minishop3/schema/minishop3.mysql.schema.xml

Main schema attributes:

  • package: MiniShop3\Model
  • baseClass: xPDO\Om\xPDOObject
  • platform: mysql
  • version: 3.0

Database tables ​

Products ​

ModelTableDescription
msProductsite_contentProduct (extends modResource)
msProductDatams3_productsProduct data (price, article, weight)
msProductFilems3_product_filesProduct gallery files
msProductOptionms3_product_optionsProduct option values
msLinkms3_linksProduct link types
msProductLinkms3_product_linksProduct-to-product links

msProductData — main fields ​

FieldTypeDescription
idintID (matches resource ID)
articlevarchar(50)Article/SKU
pricedecimal(12,2)Price
old_pricedecimal(12,2)Old price
stockdecimal(13,3)Stock quantity
weightdecimal(13,3)Weight
imagevarchar(255)Main image
thumbvarchar(255)Thumbnail
vendor_idintVendor ID
made_invarchar(100)Country of origin
newtinyint(1)"New" flag
populartinyint(1)"Popular" flag
favoritetinyint(1)"Favorite" flag
tagsjsonTags
colorjsonColors
sizejsonSizes

Categories ​

ModelTableDescription
msCategorysite_contentCategory (extends modResource)
msCategoryMemberms3_product_categoriesProduct–category relation
msCategoryOptionms3_category_optionsCategory options

Orders ​

ModelTableDescription
msOrderms3_ordersOrder
msOrderAddressms3_order_addressesOrder delivery address
msOrderProductms3_order_productsOrder line items
msOrderLogms3_order_logsOrder change history
msOrderStatusms3_order_statusesOrder statuses

msOrder — main fields ​

FieldTypeDescription
idintOrder ID
user_idintMODX user ID
customer_idintmsCustomer ID
tokenvarchar(128)Session token
uuidchar(36)Order UUID
numvarchar(20)Order number
costdecimal(12,2)Total cost
cart_costdecimal(12,2)Products cost
delivery_costdecimal(12,2)Delivery cost
weightdecimal(13,3)Total weight
status_idintStatus ID
delivery_idintDelivery method ID
payment_idintPayment method ID
contextvarchar(100)MODX context
order_commenttextOrder comment
idempotency_keyvarcharIdempotency key (programmatic orders)
createdondatetimeCreated at
updatedondatetimeUpdated at

Customers (NEW in MiniShop3) ​

ModelTableDescription
msCustomerms3_customersStore customer
msCustomerAddressms3_customer_addressesSaved customer addresses
msCustomerTokenms3_customer_tokensAuth tokens

msCustomer — main fields ​

FieldTypeDescription
idintCustomer ID
user_idintLinked modUser ID (optional)
first_namevarchar(191)First name
last_namevarchar(191)Last name
phonevarchar(50)Phone
emailvarchar(191)Email
passwordvarchar(255)Password hash
is_activetinyint(1)Active
is_blockedtinyint(1)Blocked
email_verified_atdatetimeEmail verification date
orders_countintOrder count
total_spentdecimal(12,2)Total order sum
last_order_atdatetimeLast order date
privacy_accepted_atdatetimePrivacy policy acceptance date

Difference from miniShop2

In miniShop2 customer = modUser. In MiniShop3 customer is a separate entity msCustomer, which can optionally be linked to modUser.

Delivery and payment ​

ModelTableDescription
msDeliveryms3_deliveriesDelivery methods
msPaymentms3_paymentsPayment methods
msDeliveryMemberms3_delivery_paymentsDelivery–payment link

Vendors ​

ModelTableDescription
msVendorms3_vendorsProduct vendors

Options ​

ModelTableDescription
msOptionms3_optionsOption catalog

Field configuration (NEW in MiniShop3) ​

ModelTableDescription
msModelFieldms3_model_fieldsModel field settings
msModelFieldSectionms3_model_field_sectionsField sections
msProductFieldms3_product_fieldsProduct fields (legacy)
msPageSectionms3_page_sectionsPage sections (legacy)
msExtraFieldms3_extra_fieldsExtra fields

Notifications ​

ModelTableDescription
msNotificationConfigms3_notification_configsNotification configuration

Working with models ​

Creating an order ​

php
use MiniShop3\Model\msOrder;
use MiniShop3\Model\msOrderProduct;
use MiniShop3\Model\msOrderAddress;

// Create order
$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();

// Add product to order
$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();

Working with customer ​

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

// Find or create customer
$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();
}

// Add address
$address = $modx->newObject(msCustomerAddress::class);
$address->fromArray([
    'customer_id' => $customer->get('id'),
    'city' => $city,
    'street' => $street,
    'building' => $building,
]);
$address->save();

// Get all customer addresses
$addresses = $customer->getMany('Addresses');

Iterating records ​

php
use MiniShop3\Model\msOrder;

// Correct: getIterator() — lazy loading
foreach ($modx->getIterator(msOrder::class, ['status_id' => 2]) as $order) {
    // Process order
}

// Incorrect: getCollection() loads everything into memory
// $orders = $modx->getCollection(msOrder::class, $criteria);

Model architecture ​

Difference from miniShop2 ​

miniShop2 used "fat models" — model classes contained a lot of business logic: validation, calculations, notifications, etc.

In MiniShop3 business logic lives in the service layer (src/Services/, src/Controllers/):

miniShop2 — fat model ​

MiniShop3 — model + services ​

Pragmatic approach ​

Compromise with MODX architecture

In modern approaches (Clean Architecture, DDD) models should only handle database access and not know about the service layer, repositories, or controllers.

However, MODX architecture commonly puts business logic in models (modResource, modUser, etc.). Since some MiniShop3 models extend MODX classes (msProduct extends modResource, msCategory extends modResource), they must follow similar patterns.

Therefore MiniShop3 models reference services for business logic — a pragmatic compromise for MODX ecosystem compatibility.

Example: calling a service from a model ​

php
// src/Model/msProductData.php
class msProductData extends xPDOSimpleObject
{
    public function getPrice(array $data = []): float
    {
        // Delegate to formatting service
        $ms3 = $this->xpdo->services->get('ms3');
        return $ms3->format->price($this->get('price'));
    }
}

Regenerating models ​

Models are maintained manually

In MiniShop3 models are NOT auto-generated from the XML schema via buildModel(). They are created and maintained manually in src/Model/.

This allows:

  • Custom methods in models
  • Full PHP 8 type hints
  • Full control over model code

Important: Do not run buildModel() — it will overwrite custom model methods.

Model file structure ​

src/Model/
├── msProduct.php           # Main model class
├── mysql/
│   └── msProduct.php       # MySQL-specific mapping

Example model ​

php
<?php
namespace MiniShop3\Model;

use MODX\Revolution\modResource;

class msProduct extends modResource
{
    // Custom model methods
    public function getPrice(): float
    {
        $data = $this->getOne('Data');
        return $data ? (float)$data->get('price') : 0.0;
    }
}

Example mapping ​

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',
        ],
        // ... other metadata
    ];
}

Phinx migrations ​

Schema changes are applied via Phinx migrations:

core/components/minishop3/migrations/
├── 20240101000000_create_customers_table.php
├── 20240102000000_add_customer_fields.php
└── ...

Running migrations ​

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

Example migration ​

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();
    }
}

Relation diagram ​

Product ​

msProduct (extends modResource) and msProductData (table ms3_products)

Order ​