Orm

Orm is short for Object Relational Mapper which does 2 things: it maps your database table rows to objects and it allows you to establish relations between those objects.
It follows closely the Active Record Pattern, but was also influenced by other systems.

CRUD: Create, Read, Update and Delete

Once you've configured your Model it's time to start creating, reading, updating and deleting entries in your database.

Create

// option 1
$new = new Model_Example();
$new->property = 'something';
$new->save();

// option 2, use forge instead of new
$new = Model_Example::forge();
$new->property = 'something';
$new->save();

After save() the model has been saved to the database and if you're using an auto_increment primary key it will automatically be set on the instance after successful saving it.

You can also set properties on the model from an array:

$props = array('property' => 'something');

// using "new"
$new = new Model_Example($props);
$new->save();

// option 2, use forge instead of new
$new = Model_Example::forge($props);
$new->save();

Read

Or actually find(). The find method can be used in 3 ways: find a specific id (primary key), find first/last or all entries with conditions. All possible selection methods (where, or_where, order_by, etc) can be found at the bottom of the page.

Find by ID

// you know there's an article with ID=2
$entry = Model_Article::find(2);

// ...or when using compound primary keys
$entry = Model_Article::find(array(2, 'foo'));

In this example it will return either an instance of Model_Article or null when the ID wasn't found.

Find first/last

// find the first entry
$entry = Model_Article::find('first');

// find the last entry added when ordered by date
$entry = Model_Article::find('last', array('order_by' => 'date'));

In this example it will return either an instance of Model_Article or null when the ID wasn't found.

Find all

// find all articles
$entry = Model_Article::find('all');

// find all articles from category 1 order descending by date
$entry = Model_Article::find('all', array(
	'where' => array(
		array('category_id', 1),
	),
	'order_by' => array('date' => 'desc'),
));

// find all articles from category 1 or category 2
$entry = Model_Article::find('all', array(
	'where' => array(
		array('category_id', 1),
		'or' => array(
			array('category_id', 2),
		),
	),
));

In this example it will always return an array of instances of Model_Article.

Find using method chaining

You can not call the find() method without properties, or call it with null or a variable that contains null as argument. If you do, null is returned. If you want to chain methods, use the query() method instead.

$query = Model_Article::query()->where('category_id', 1)->order_by('date', 'desc');

// We want to know the total number of articles for pagination
$number_of_articles = $query->count();

// We want to know the last id issued
$number_of_articles = $query->max('id');

// We want to know the date of the first article posted
$number_of_articles = $query->min('date');

// fetch one Article
$newest_article = $query->get_one();

// we re-use but add a return limitation to the previous query to fetch multiple articles
$all_articles = $query->limit(15)->get();

All these methods are equally valid, the four other methods of find actually use the Query object as well but don't return it.

Converting to an array

You can convert a single query result to an array using the to_array() method


// using the chaining method. select one row and convert it to an array
$entry = Model_Article::query()->where('id', '=', 8)->get_one()->to_array();

// select one row, convert it to an array, include custom data and also run recursivly
$entry = Model_Article::query()->where('id', '=', 8)->get_one()->to_array(true, true);

// select one row and convert it to an array, include custom data, run recursivly and get eav relations
$entry = Model_Article::query()->where('id', '=', 8)->get_one()->to_array(true, true, true);

Partial column selects

By default all ORM find methods will select all table columns. You can use the select array entry or the select() method to alter this behavior.

// using the array method. select only the 'name' and 'date' columns
$entry = Model_Article::find('last', array('select' => array('name', 'date')));

// same, but then using the chaining method
$entry = Model_Article::query()->select('name', 'date')->get();

// using the array method. select all columns except the 'date' column
$entry = Model_Article::find('all', array('select' => array(array('date' => false))));

// same, but then using the chaining method
$entry = Model_Article::query()->select(array('date' => false))->get();

Update

$entry = Model_Article::find(4);
$entry->title = 'My first edit';
$entry->author = 'Total n00b';
$entry->save();

That's it, nothing more to it: Find, change properties and save.

You can also set properties on the model from an array:

$entry = Model_Article::find(4);
$entry->set(array(
	'title'  => 'My first edit',
	'author' => 'Total n00b'
));

$entry->save();

Delete

$entry = Model_Article::find(4);
$entry->delete();

Again nothing more to it: Find and delete.

In the previous example the $entry variable and its object still exist. The primary keys are however set to null and the object itself is considered a new instance. If you save it after deletion it will be re-entered into the database and be given a new primary key when auto_increment is switched on.

All selection methods when using find

Method Params Examples
select string $column, [string $more_columns,] | array $filter
// Find only some columns
Model_Article::query()->select('id', 'name');
Model_Article::find('all', array('select' => array('id', 'name')));

// Find all columns except 'name'
Model_Article::query()->select(array('name' => false));
Model_Article::find('all', array('select' => array(array('name' => false))));
related string|array $relation, [array $conditions]
// include related models in the find
Model_Article::query()->related(array('author', 'comments'));
Model_Article::find('all', array('related' => array('author', 'comments')));

//=============================
// Include one or more conditions on a relation (only supported when chaining)
//=============================

// First method - using an array
//----------------------------
// one condition
Model_Article::query()
    ->related('author', array('where' => array(array('active', '=', 1))));

// multiple conditions
Model_Article::query()
    ->related('author', array('where' => array(array('active', '=', 1), array('status', '=', 'X'))));

// Second method - use dot notation
//----------------------------
// one condition
Model_Article::query()
	->related('author')
	->where('author.active', 1);

// multiple conditions
Model_Article::query()
	->related('author')
	->where('author.active', 1);
	->where('author.status', 1);
use_view string $viewname
// use a view to retrieve data
Model_Article::query()->use_view('with_comments');
Model_Article::find('all', array('use_view' => 'with_comments'));
where string $column, [string $operator,] mixed $value
// Single where
Model_Article::query()->where('id', 4);
Model_Article::find('all', array('where' => array('category_id' => 5)));

// Multiple where usage examples
Model_Article::query()->where('id', 4)->where('category_id', '>', 1);
Model_Article::query()->where(array('id' => 4, 'category_id' => 6));
Model_Article::find('all', array('where' => array(array('category_id', '=', 5), array('publish', '<', time()))));

// Using or where
Model_Article::query()->where('id', 4)->or_where('id', 5);
order_by string $column, [string $direction]
// Single order_by
Model_Article::query()->order_by('name', 'desc');
Model_Article::find('all', array('order_by' => array('name' => 'desc')));

// Multiple order_by usage examples
Model_Article::query()->order_by('name', 'desc')->order_by('category_id', 'asc');
Model_Article::query()->order_by(array('name' => 'desc', 'category_id' => 'asc'));
Model_Article::find('all', array('order_by' => array(array('name' => 'desc', 'category_id' => 'asc'))));
from_cache bool$cache
// disable the ORM object cache on this query
Model_Article::query()->from_cache(false);
Model_Article::find('all', array('from_cache' => false));
limit int $limit
// limit with relation consistence
Model_Article::query()->limit(10);
Model_Article::find('all', array('limit' => 10));
rows_limit int $limit
// limit without relation consistence
Model_Article::query()->rows_limit(10);
Model_Article::find('all', array('rows_limit' => 10));
offset int $offset
// offset with relation consistence
Model_Article::query()->offset(10);
Model_Article::find('all', array('offset' => 10));
rows_offset int $offset
// offset without relation consistence
Model_Article::query()->rows_offset(10);
Model_Article::find('all', array('rows_offset' => 10));

Do not mix the two limit/offset types! A combination of limit and rows_offset (or the other way around) will lead to unexpected results!

In this context, relation consistence means that by default when you query including related models, the ORM will make sure the related results are consistent. This means that your results aren't always exactly limited. For example, if the first parent in the result has 12 children, your resultset will contain 12 records even if you have used limit(10). When you use rows_limit(10), the resultset will only contain 10 records, but 2 related records will be missing, and can not be retrieved later because the results will be cached and the cache handler doesn't know the results were incomplete!

Complex where clauses

Using chaining you can create more complex where clauses, using and, or and nesting

// complex where clause
Model_Article::query()
  ->where('author', '=', 16)
  ->and_where_open()
      ->where('date', '<', time())
      ->or_where('draft', '=', 1)
  ->and_where_close();

// creates WHERE `author` = 16 AND (`date` < 1348404127 OR `draft` = 1)

To create a nested OR, use or_where_open() and or_where_close(). Always make sure your open and close methods match!

Subqueries

Subqueries can be created by constructing a seperate query object and using get_query() to parse it.

$subQuery = Model_Article::query()
        ->select('author')
        ->where('date', '<', time())
        ->where('draft', '=', 1);

Model_Article::query()
        ->where('author', '=', 16)
        ->or_where('author', $subQuery->get_query(true))
        ->get();

Custom SQL

It is also possible to perform custom SQL statements and have the result returned as an Orm model (or list of models). Although this is not part of the Orm itself it is useful to know. Take a look at the DB::query() function for more information.

DB::query('SELECT * FROM `articles` WHERE `id` = 1')->as_object('Model_Article')->execute();

It is recommended that you use the query() function where ever possible though