Форумы / Cotonti / Extensions / SimpleORM

A basic ORM for Cotonti

GHengeveld
#1 21.11.2011 11:43

Recently I've been working on a basic ORM tool for Cotonti. We plan to bundle an ORM similar to PHP ActiveRecord with Cotonti 1.0.

For those who don't know what ORM is: Object-Relational Mapping is a tool that lets you query and manipulate data from a database using an object paradigm. It encapsulates the code needed to manipulate the data, so you don't write SQL statements anymore. An ORM tool is often used to implement the 'model' part of the MVC design pattern, and I intend to do exactly that. Basically it makes module development a lot easier because you only have to define your object properties in one place and everything else will then be handled for you, including validations, database queries and caching.

SimpleORM consists of only one file with one abstract class. It's intended to be placed inside your Cotonti system folder, so you can easily include it in your modules with using cot_incfile(). The code is on Github: https://github.com/GHengeveld/cot-simpleorm. It's BSD licensed so feel free to use it, fork it, modify it, etc.

Note that PHP 5.3 is required.

Implementation

First, make sure you include the SimpleORM file:

require_once cot_incfile('simpleorm');

Your module should have a folder named 'classes', in which you will store your model classes. As an example I will use a module named 'issuetracker', which has a model named Project, stored in modules/issuetracker/classes/Project.php:

class Project extends SimpleORM
{
	protected $table_name = 'projects';
	protected $columns = array(
		'id' => array(
			'type' => 'int',
			'primary_key' => true,
			'auto_increment' => true,
			'locked' => true
		),
		'ownerid' => array(
			'type' => 'int',
			'foreign_key' => 'users:user_id',
			'locked' => true
		),
		'name' => array(
			'type' => 'varchar',
			'length' => 50,
			'unique' => true
		),
		'desc' => array(
			'type' => 'text'
		),
		'type' => array(
			'type' => 'varchar',
			'length' => 6
		),
		'created' => array(
			'type' => 'int',
			'on_insert' => 'NOW()',
			'locked' => true
		),
		'updated' => array(
			'type' => 'int',
			'on_insert' => 'NOW()',
			'on_update' => 'NOW()',
			'locked' => true
		)
	);
}

The model class extends SimpleORM, which will make Project inherit all of SimpleORM's methods and properties. Since SimpleORM 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. SimpleORM will automatically append Cotonti's $db_x to the table name.

$columns is where things get interesting. It is where you configure the database columns for the objects. SimpleORM 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.

Adding a project

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

if ($name && $type)
{
	$obj = new Project(array(
		'name' => $name,
		'desc' => $desc,
		'type' => $type,
		'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.

Listing projects

To get existing objects from the database, SimpleORM 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

SimpleORM 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('simpleorm');

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

The file 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.

Отредактировано: GHengeveld (08.02.2012 15:55, 12 лет назад)