DataMapper ORM


Advanced Relationships

Datamapper ORM has extended the ability of DataMapper to handle significantly more complex relationships, including:

More Advanced Relationship Overview

Before showing examples, let's review a normal relationship, and the information needed to make it work. A normal relationship between two models is managed by the database structure, and a value stored on both models, in the $has_many and $has_one arrays. This value tells DataMapper to look for the related model. Internally, DataMapper knows that this model is related using the generated table and model names.

With advanced relationships, we can override the generated information, and even replace the name used to look for the relationship. This allows us to relate the same object multiple times, as well as relate an object to itself.

Extended Relationship Attributes

Previously, a single value was stored per object in the $has_many and $has_one arrays. To begin making more advanced relationships, we convert them into a relationship key and attribute array():

Before

class Post extends DataMapper {
    $has_one = array('user');
}

After

class Post extends DataMapper {
    $has_one = array(
        'user' => array()
    );
}

Right now, nothing different will happen. User will still be related to Post as $post->user. To change the user into something else, we'll need to use some of the following four attributes. You can specify any combination of them, but the most common are class and other_field together.

Relationship name

The key of the relationship array entry defines the relationship name. It is the one used in the code when you want to query the relation. In the example given above, the key used is user, so you would access this relation by using:

// define a new relationship for the Posts model
$p = new Post(1);
$p->user->get();

Attributes Table

Name Description Default Common Usage
class Tells DataMapper which class (model) this relationship points to. key used on the array entry Almost always specified. Use it when the class name differs from the key used.
other_field Tells DataMapper what the relationship looks like from class. $this->model Whenever the key relation of the other model is different than this models name.
join_other_as Override the generated column name for the other model. key on $has_many or $has_one Used with custom column names, or in some unusual self-relationships.
join_self_as Override the generated column name for this model. other_field Used with custom column names, or in some unusual self-relationships.
join_table Override the generated table name for this relation. '' Custom table name. Also used in some multi-table (more than two) relationships.
model_path Alternative location for this model. APPPATH Used in a modular context, where models are used cross-module. Note that is it the module path, NOT the model path!
auto_populate Autopopulate the objects for this relation. FALSE Use the same as the global settings $auto_populate_has_one and $auto_populate_has_many, but then on a per-relationship basis.
cascade_delete Delete the relation when deleting the parent object. TRUE Use the same as the configuration variable and the global setting $cascade_delete, but then on a per-relationship basis.

Manually defining the relationship

Under normal circumstances, this is not something you should do, but it will help you understand how Datamapper generates the relationship definition between two models, and what the role of the different fields in the relationship definition is.

Consider the models Author and Book. An author can write multiple books, a book can have multiple writers, which makes this a many-to-many relationship. You can define this by a simple has_many entry in both models, and Datamapper will generate the rest. The following definition shows you the advanced relationship definition that Datamapper will generate internally for this relation.

Author

class Author extends DataMapper {
    $has_many = array(
        'book' => array(			// in the code, we will refer to this relation by using the object name 'book'
            'class' => 'book',			// This relationship is with the model class 'book'
            'other_field' => 'author',		// in the Book model, this defines the array key used to identify this relationship
            'join_self_as' => 'author',		// foreign key in the (relationship)table identifying this models table. The column name would be 'author_id'
            'join_other_as' => 'book',		// foreign key in the (relationship)table identifying the other models table as defined by 'class'. The column name would be 'book_id'
            'join_table' => 'authors_books')	// name of the join table that will link both Author and Book together
    );
}

Book

class Book extends DataMapper {
    $has_many = array(
        'author' => array(			// in the code, we will refer to this relation by using the object name 'author'
            'class' => 'author',		// This relationship is with the model class 'author'
            'other_field' => 'book',		// in the Author model, this defines the array key used to identify this relationship
            'join_self_as' => 'book',		// foreign key in the (relationship)table identifying this models table. The column name would be 'book_id'
            'join_other_as' => 'author',	// foreign key in the (relationship)table identifying the other models table as defined by 'class'. The column name would be 'author_id'
            'join_table' => 'authors_books')	// name of the join table that will link both Author and Book together
    );
}

Some common naming rule deviations

You want to add a prefix or suffix to your class names to avoid class name collisions (with for example controllers or CodeIgniter libraries).

You want to use alternative names to specify the relationship in the code. Say want to use $obj->writer instead of $obj->author.

You want to use alternative table name for the relation. For example, you prefer 'written_books'.

You want to use alternative name for a foreign key column name. Say want to use writer_id instead of author_id.

Multiple Relationships to the Same Model

This is the most common usage, and is used in almost every project. There is a simple pattern to defining this relationship.

Post has a creator and an editor, which may be different users. Here's how to set that up.

Post

class Post extends DataMapper {
    $has_one = array(
        'creator' => array(
            'class' => 'user',
            'other_field' => 'created_post'
        ),
        'editor' => array(
            'class' => 'user',
            'other_field' => 'edited_post'
        )
    );
}

User

class User extends DataMapper {
    $has_many = array(
        'created_post' => array(
            'class' => 'post',
            'other_field' => 'creator'
        ),
        'edited_post' => array(
            'class' => 'post',
            'other_field' => 'editor'
        )
    );
}

A couple of things to note here.

Many-to-Many Reciprocal Self Relationships

In this type of relationship, you have records that related to each other, and where saving or deleting the relationship in one direction should also maintain the relationship in the reverse direction.

The best example I can think of is in the area of genealogy.

Suppose you have a table with information about people, and you want to relate them (grandparent-parent-child etc). You always have to make the relation both ways, since it can’t be that person A is the child of person B, but B is not the parent of A. If you define this relation as reciprocal, when you save the relation between A and B, automatically the relation between B and A is saved as well. And when you delete the relationship, both relations are deleted.

class Person extends DataMapper {
    $has_many = array(
        'related_person' => array(
            'class' => 'person',
            'other_field' => 'person',
            'reciprocal' => TRUE
        ),
        'person' => array(
            'other_field' => 'related_person',
            'reciprocal' => TRUE
        )
    );
}

To get this to work, you will need:

Note that this defined per relation. If you want this to work both ways, you need to specify the 'reciprocal' setting on BOTH relationship definitions.

Setting up the Table Structure with Advanced Relationships

The table structure has one key difference. While the names of the tables is still determined using the plural form of the model, the column names are now defined using the relationship key.

In-table Foreign Keys

If we decide to use in-table foreign keys, the posts table looks like this:

id title body creator_id editor_id
1 Hello World My first post 4 4
2 Another Post My second post (Edited by Joe) 4 6

Dedicated Join Table

If we decide to use a join table, that table is a little different. The table is still called posts_users, but the table now looks like this:

id creator_id created_post_id editor_id edited_post_id
1 4 1 NULL NULL
2 NULL NULL 4 1
3 4 2 NULL NULL
4 NULL NULL 6 2

This stores the same information. We only have the option in this case because the posts side was $has_one. If posts could have many creators or many editors, then that would have to be stored in this table.

Multi-table Relationships

In normal circumstances, you will always have a relationship between two tables, either in a one-to-one, one-to-many, or many-to-many relationship.

Occasionally however, you might have a need to define a relationship between more than two tables. In this case, you will have to create a joined table with more than two foreign keys, one to each of the tables involved in the relationship. In this situation Datamapper can no longer automatically generate the name of the joined table. Instead, you will have to use the join_table to manually define the name of the joined table.

When you use this option, make sure you define the same table name in all models!

Self Relationships

Technically, self-relationships are the same as having multiple relationships to the same object. There is one key difference: the table names. First, we'll set the class up, then I'll show you the table name.

Post has Many Related Posts

We want to have the ability to track related posts. Here's the model:

class Post extends DataMapper {
    $has_one = array(
        'creator' => array(
            'class' => 'user',
            'other_field' => 'created_post'
        ),
        'editor' => array(
            'class' => 'user',
            'other_field' => 'edited_post'
        )
    );
    $has_many = array(
        'relatedpost' => array(
            'class' => 'post',
            'other_field' => 'post'
        ),
        'post' => array(
            'other_field' => 'relatedpost'
        )
    );
}

Some notes about this form:

Naming Self-Relationship Tables

Self relationships are special because the join table name is not generated from the table name of the object, but instead from the relationship keys used to define the relationship.

For the example above, the table looks like this:

posts_relatedposts

id post_id relatedpost_id
1 1 2
2 2 1

This allows us to relate Post #1 -> Post #2, as well as relating Post #2 -> Post #1.

Defining relations after the model has been loaded

Sofar, all relationship definitions have been static, defined as a property of the datamapper model.

For most applications, this works fine. However, there are situation where you would like to define the relationships between models at runtime. This is particularly true in a modular environment, where a module can introduce new models that have a relationship with models from an other module, or from the application itself.

Obviously you can't define these relationships in the model itself, as the name of the model might not be known at the time of writing, or the models database structure might not be available until the module has been installed.

Runtime relationship definition

Until now, to define a relationship you are used to code it like this:

class Post extends DataMapper {
    $has_one = array('user');
}

This relation can also be defined at runtime:

// define a new relationship for the Posts model
$p = new Post();
$p->has_one('user');

You can also do this for advanced relationships. The example used previously

class Post extends DataMapper {
    $has_one = array(
        'creator' => array(
            'class' => 'user',
            'other_field' => 'created_post'
        ),
        'editor' => array(
            'class' => 'user',
            'other_field' => 'edited_post'
        )
    );
}

can be defined at runtime like this:

// define new relationships for the Posts model
$p = new Post();
$relation = array(
	'class' => 'user',
	'other_field' => 'created_post'
);
$p->has_one('creator', $relation);

$relation = array(
	'class' => 'user',
	'other_field' => 'edited_post'
);
$p->has_one('editor', $relation);

And there you have it. Advanced relationships to allow you to manage more complex data structures. On to DataMapper in Controllers so we can actually use this information!