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.

Relations: Many to Many

Specifies a many-to-many relationship to another model. The target model will have the same type of relationship in the other direction. To allow for these relations you need a table in between with just the 2 IDs from both sides of the relation as dual-primary key.

Keeping values with the relationship

If you need to save values with the relationship table you don't want to use ManyMany but create a model in between instead which belongs to both sides of what you would otherwise call the ManyMany relationship. It is described under "Usage with nested relationships how you can fetch all this.

Configuration Options

ManyMany relations have three additional configuration options compared to the others. All of these are still optional when using the most common naming conventions.

Option Default Notes
table_through Calculated from model_to and model_from alphabetically ordered This is the table that connects the 2 models and has both their IDs in it. For 2 models like Model_User and Model_Post it will be named posts_users by default (both plural).
key_through_from Calculated from the current model name The key that matches the current model's primary key. If your current model is Model_Post this will be post_id by default
key_through_to Calculated from the related model name The key that matches the related model's primary key. If your related model is Model_User this will be user_id by default

Ordering on a column in the through table

In addition to the normal default order you can define in a relationship definition, you can also define now the records from table_through need to be ordered:

protected static $_many_many = array(
	'users' => array(
		'table_through' => 'posts_users', 	// both models plural without prefix in alphabetical order
		'conditions' => array(
		   'order_by' => array(
				'posts_users.status' => 'ASC'	// define custom through table ordering
			),
		),
	)
);
// other fields that may be required have been ommitted for this example

The conditions array can also contain where clauses, which will act as a permanent filter on the related table results.

Example

Let's say we have a model Model_Post and it has many and belongs to many Model_Users. The ID of the Model_Post is along with the ID of the Model_User in a table called posts_users (default order is alphabetical). That table has just 2 columns: post_id and user_id which are together the primary key of that table.
If you keep to the defaults all you need to do is add 'users' to the $_many_many static property of the Model_Post:

protected static $_many_many = array('users');

And you need to add a table like this one to your SQL:

CREATE TABLE IF NOT EXISTS `posts_users` (
  `post_id` int(11) NOT NULL,
  `user_id` int(11) NOT NULL,
  PRIMARY KEY (`post_id`,`user_id`)
);

Below are examples for establishing and breaking has-many relations:

// both main and related object are new:
$post = new Model_Post();
$post->users[] = new Model_User();
$post->save();

// both main and related object already exist
$user = Model_User::find(8);
$user->posts[1] = Model_Post::find(1);
$user->save();

// break the relationship established above
$post = Model_Post::find(1);
unset($post->users[8]);
$post->save();

Full config example with defaults as values

// in a Model_Post which has and belongs to many Users
// = multiple posts per user and multiple users (authors) per post
protected static $_many_many = array(
	'users' => array(
		'key_from' => 'id',
		'key_through_from' => 'post_id', // column 1 from the table in between, should match a posts.id
		'table_through' => 'posts_users', // both models plural without prefix in alphabetical order
		'key_through_to' => 'user_id', // column 2 from the table in between, should match a users.id
		'model_to' => 'Model_User',
		'key_to' => 'id',
		'cascade_save' => true,
		'cascade_delete' => false,
	)
);