Skip to content
This repository

ORM 

ghengeveld edited this page · 30 revisions

Pages 2

Clone this wiki locally

CotORM

CotORM is a basic ORM tool for the Cotonti CMF. It consists of only one file with one abstract class. It's intended to be placed inside the Cotonti system folder, so it can be easily included in modules using cot_incfile().

Table of contents

Introduction

CotORM follows the MVC pattern, but doesn't force you to do so. While combining it with the MVC helper is the most natural choice, CotORM also allows you to write your functional code in the classic, procedural way which is common in most Cotonti modules and plugins. However, the examples below assume you're using the MVC helper. Here's an overview of file locations:

Classic MVC
Models modules/issuetracker/classes modules/issuetracker/models
Controllers modules/issuetracker/inc modules/issuetracker/controllers
Views modules/issuetracker/tpl modules/issuetracker/views

By method of documentation, we'll be implementing a module named 'issuetracker'. A simple issue tracker could contain Projects, Milestones and Issues. The code fragments below show how one could implement the Projects part of the module.

Models

One of the main advantages of using an ORM is the central definition of models, which are used by the ORM to perform many automated tasks. One could argue that a good model definition is the most important part of your module. Assuming you're building something that relies heavily on database storage, of course.

Defining your models is very important because it's the central place for configuring CotORM's behavior. If you do not configure it correctly, you will leave security holes and possibly introduce bugs. In a worst-case scenario, you may even lose your data, although that's very unlikely. Make sure you utilise properties such as 'hidden' and 'locked' where appropriate.

As indicated above, your module should have a folder named 'models', in which you will store your model classes. If you prefer not to use the MVC helper, you can store your models in a folder named 'classes' instead. Cotonti's built-in autoloader will already look for files there.

modules/issuetracker/models/Project.php:

<?php

defined('COT_CODE') or die('Wrong URL.');

class Project extends CotORM
{
    protected $table_name = 'projects';
    protected $columns = array(
        'id' => array(
            'type' => 'int',
            'primary_key' => true,
            'auto_increment' => true,
            'locked' => true // An ID can never change
        ),
        'ownerid' => array(
            'type' => 'int',
            'foreign_key' => 'users:user_id', // Verifies that the provided owner ID exists in the users table
            'locked' => true // Changing project ownership is not allowed
        ),
        'name' => array(
            'type' => 'varchar',
            'length' => 50,
            'unique' => true // project name must be unique
        ),
        'metadata' => array(
            'type' => 'object' // This can be used to store arbitrary data related to the project
        ),
        'state' => array(
            'type' => 'enum',
            'options' => array('pending', 'active', 'closed') // A project is always in one of these states
        ),
        'created' => array(
            'type' => 'int',
            'on_insert' => 'NOW()', // Sets current timestamp when project is created
            'locked' => true // Can't be changed afterwards
        ),
        'updated' => array(
            'type' => 'int',
            'on_insert' => 'NOW()',
            'on_update' => 'NOW()', // Sets current timestamp when project is updated
            'locked' => true // Can't be changed by the user
        )
    );
}

?>

Because the model class extends CotORM, it will inherit all of CotORM's methods and properties. Since CotORM contains all the fancy methods, all we need to do here is configure the properties of Project. There are two properties we have to configure: $table_name and $columns.

$table_name is the name of the database table to store Project objects. A common convention is to use the lowercase, plural of the class name, so 'projects' in this case. CotORM will automatically prepend Cotonti's $db_x to the table name.

$columns is where things get complicated. It is where you configure the database columns for the objects. CotORM will automatically validate incoming data based on the rules set in $columns. This includes variable type checking, foreign key constraints and unique values. It also allows you to 'lock' and/or 'hide' a column from the outside world. Here's an overview of allowed properties:

Column properties

Property Datatype Default Description
type string - Always required. MySQL data type (lowercase), such as 'int', 'varchar', 'text' or one of the special values such as 'enum' (see below).
required bool false Flag the field as required. Will accept NULL if 'nullable' => true.
nullable bool FALSE If true, the MySQL column will accept NULL values and 'required' and 'minlength' flags will be ignored if the passed value is NULL. Also, default_value will be ignored on update if NULL is passed and 'enum' columns will accept NULL aside from their regular options.
signed bool false Flag the field as signed (allow negative values). For numeric types only. Unsigned is used by default.
minlength int 0 Minimum string length of the value.
maxlength int, string varchar: 255, others: - Maximum display length of the value, or in case of float or decimal, a string representing precision and scale (see MySQL manual).
primary_key bool false If true, the column will be considered the primary key and be used as object identifier.
foreign_key string - Table and column name pair which the column is directly related to. CotORM will enforce the foreign key dependency. Table and column name must be seperated with a colon. Table name should not include `$db_x`. Example: 'users:user_id'
index bool false If true, sets an MySQL INDEX on this column.
unique bool false If true, sets an MySQL UNIQUE constraint on this column.
auto_increment bool false If true, sets the MySQL AUTO_INCREMENT flag on this column.
default_value string, int, float - MySQL DEFAULT value. If foreign_key is given, this is the only value which will pass even if such a foreign record doesn't exist.
on_insert string, int, float - Default value for the column in INSERT queries. Accepts several special values, see the listing below.
on_update string, int, float - Default value in UPDATE queries. Also accepts special values.
locked bool false Disallows UPDATE queries on this column.
hidden bool false Makes the column not appear in result objects.
alphanumeric bool false Enforces values to be alphanumeric.
options array - Required for 'enum' columns. Numeric array of allowed values. The first option becomes the default_value, unless another default_value is defined.
Special column types
  • object: Allows storage of PHP objects. Data will automatically be serialized/unserialized and stored as text.
  • enum: Accepts values in a predefined set of options. Allowed values must be defined in the 'options' property as a numeric array. Will also accept NULL if 'nullable' => true.
Special values for on_insert and on_update
  • NOW() => current UNIX time (integer)
  • RANDOM() => random alphanumeric string or integer of maxlength length.
  • INC() => Increase by one ($value++). Sets 0 on_insert.
  • DEC() => Decrease by one ($value--). Sets 0 on_insert.

Creating a project

$name = cot_import('name', 'P', 'TXT', 50);
$desc = cot_import('desc', 'P', 'TXT');

if ($name && $type)
{
    $obj = new Project(array(
        'name' => $name,
        'metadata' => array(
            'description' => $desc
        ),
        'ownerid' => $usr['id']
    ));
    if ($obj->insert())
    {
        // succesfully added to database
    }
}

The insert() and update() methods are wrappers for a more generic function called save(). This method can take one argument, which can either be 'insert' or 'update'. If you don't pass this argument it will try to update an existing record and if that fails try to insert a new record. The save() method has 3 possible return values: 'added', 'updated' or null. insert() and update() return a boolean.

Finding and listing projects

To get existing objects from the database, CotORM provides three 'finder methods'. These basically run a SELECT query on the database and return rows as objects of the type the finder method was executed on. The three variants are find(), findAll() and findByPk(), which respectively will return an array of objects matching a set of conditions, return an array of all objects or return a single object matching a specific primary key.

Here's an example use case, listing all projects and assigning data columns to template tags:

$projects = Project::findAll($limit, $offset, $order, $way);
if ($projects)
{
    foreach ($projects as $project)
    {
        foreach ($project->data() as $key => $value)
        {
            $t->assign(strtoupper($key), $value, 'PROJECT_');
        }
        $t->parse('MAIN.PROJECTS.ROW');
    }
    $t->parse('MAIN.PROJECTS');
}

This is convenient for lists, but what about a details page of a specific object? Here's how to do that:

$id = cot_import('id', 'G', 'INT');
$project = Project::findByPk($id);
foreach ($project->data() as $key => $value)
{
    $t->assign(strtoupper($key), $value, 'PROJECT_');
}

Module setup

CotORM provides a way to simplify the install and uninstall files of your module. It has two useful methods for setup, createTable() and dropTable(). createTable() will create the table based on the configuration provided in the model. For example, issuetracker.install.php file may look like this:

require_once cot_incfile('orm', 'core');

Project::createTable();
Milestone::createTable();
Issue::createTable();

issuetracker.uninstall.php will look similar, except that it should call dropTable() instead of createTable(). Of course you might choose not to drop the tables upon uninstallation, but that's your choice as a developer.

Something went wrong with that request. Please try again.