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.

Attributes Table

Name Description Default Common Usage
class Tells DataMapper which class (model) this relationship points to. key on $has_many or $has_one Almost always specified.
other_field Tells DataMapper what the relationship looks like from class. $this->model Whenever this object is has a different name on the related object.
join_other_as Override the generated column name for the other model. key on $has_many or $has_one Rarely used, except some unusual self-relationships.
join_self_as Override the generated column name for this model. other_field Rarely used, except some unusual self-relationships.

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.

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.

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.


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!