CMS Made Simple™ Developer Resources

Schema Management

As your module evolves, you'll need to add columns, create new tables, and migrate data. CMSMS handles this through the method.upgrade.php file. This page also covers the CmsDbQueryBase class for building reusable, paginated query objects.

Incremental Upgrades

The method.upgrade.php file receives the previously installed version in the $oldversion variable. Use version_compare() to apply changes incrementally:

<?php
if (!defined('CMS_VERSION')) exit;

$db = $this->GetDb();
$dict = NewDataDictionary($db);

// Version 1.1: add a category column
if (version_compare($oldversion, '1.1', '<')) {
    $sqlarray = $dict->AddColumnSQL(
        CMS_DB_PREFIX . 'mod_holidays',
        'category C(100)'
    );
    $dict->ExecuteSQLArray($sqlarray);
}

// Version 1.2: add an index and a new preference
if (version_compare($oldversion, '1.2', '<')) {
    $sqlarray = $dict->CreateIndexSQL(
        'idx_holidays_cat',
        CMS_DB_PREFIX . 'mod_holidays',
        'category'
    );
    $dict->ExecuteSQLArray($sqlarray);
    $this->SetPreference('default_category', '');
}

// Version 2.0: create a new related table
if (version_compare($oldversion, '2.0', '<')) {
    $flds = "
        id I KEY AUTO,
        holiday_id I NOTNULL,
        tag C(100) NOTNULL
    ";
    $sqlarray = $dict->CreateTableSQL(CMS_DB_PREFIX . 'mod_holidays_tags', $flds);
    $dict->ExecuteSQLArray($sqlarray);
    $this->CreateEvent('HolidayTagged');
}

// Version 2.1: migrate data
if (version_compare($oldversion, '2.1', '<')) {
    // Move category strings into the new tags table
    $rows = $db->GetAll('SELECT id, category FROM ' . CMS_DB_PREFIX . 'mod_holidays WHERE category != ?', ['']);
    foreach ($rows as $row) {
        $db->Execute(
            'INSERT INTO ' . CMS_DB_PREFIX . 'mod_holidays_tags (holiday_id, tag) VALUES (?, ?)',
            [$row['id'], $row['category']]
        );
    }
}

Key rules:

  • Never assume the upgrade is from the immediately previous version — a user might jump from 1.0 to 2.1.
  • Each version_compare() block should be independent and idempotent where possible.
  • Test upgrades from multiple starting versions.

The CmsDbQueryBase Class

For modules that display lists of items with pagination, CMSMS provides the abstract CmsDbQueryBase class. It handles query execution, pagination, and conversion of database rows into PHP objects.

Creating a query class

Extend CmsDbQueryBase and implement the two abstract methods — execute() and GetObject():

<?php
// lib/class.HolidayQuery.php

class HolidayQuery extends CmsDbQueryBase
{
    public function execute()
    {
        if (!is_null($this->_rs)) return; // Don't execute twice

        $sql = 'SELECT SQL_CALC_FOUND_ROWS H.*
                FROM ' . CMS_DB_PREFIX . 'mod_holidays H';

        // Handle filtering
        $where = [];
        if (isset($this->_args['published'])) {
            $where[] = 'published = ' . (int) $this->_args['published'];
        }
        if (!empty($where)) {
            $sql .= ' WHERE ' . implode(' AND ', $where);
        }

        $sql .= ' ORDER BY the_date DESC';

        $db = \cms_utils::get_db();
        $this->_rs = $db->SelectLimit($sql, $this->_limit, $this->_offset);
        if ($db->ErrorMsg()) {
            throw new \CmsSQLErrorException($db->sql . ' -- ' . $db->ErrorMsg());
        }
        $this->_totalmatchingrows = $db->GetOne('SELECT FOUND_ROWS()');
    }

    public function &GetObject()
    {
        $obj = new HolidayItem();
        $obj->fill_from_array($this->fields);
        return $obj;
    }
}

Using the query class

// In your action file
$query = new HolidayQuery(['published' => 1]);
$holidays = $query->GetMatches();
// Returns: array of HolidayItem objects

// Pagination info
$total = $query->TotalMatches();   // Total rows matching (ignoring limit)
$count = $query->RecordCount();    // Rows in current page
$pages = $query->numpages;         // Total number of pages

Setting the page limit

// In the query class constructor
public function __construct($args = '')
{
    parent::__construct($args);
    if (isset($this->_args['limit'])) {
        $this->_limit = (int) $this->_args['limit'];
    }
}

// Usage
$query = new HolidayQuery(['published' => 1, 'limit' => 20]);

Manual iteration

Instead of GetMatches(), you can iterate through results manually:

$query = new HolidayQuery(['published' => 1]);
$query->execute();

while (!$query->EOF) {
    $holiday = $query->GetObject();
    // process $holiday...
    $query->MoveNext();
}

CmsDbQueryBase API Reference

Method / Property Description
execute() Abstract — build and execute the SQL query, populate $_rs and $_totalmatchingrows
GetObject() Abstract — convert the current row ($this->fields) into a PHP object
GetMatches() Execute and return all results as an array of objects
TotalMatches() Total rows matching the query (ignoring limit/offset)
RecordCount() Number of rows in the current page
MoveNext() Advance to the next row
MoveFirst() / Rewind() Reset to the first row
EOF() True if past the last row
Close() Free the result set resources
$_args (protected) Arguments passed to the constructor
$_limit (protected) Page limit (default: 1000)
$_offset (protected) Row offset (default: 0)
numpages (read-only) Total number of pages
fields (read-only) Associative array of the current row

Next Steps

This completes the Database Operations chapter. Continue to Templates to learn how to use Smarty templates for admin and frontend output.