Advanced Relationships
Datamapper ORM has extended the ability of DataMapper to handle significantly more complex relationships, including:
- Multiple Relationships to the Same Model
- Proper Self-Relationships
- Storing Additional Information on the Join Table
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 |
---|---|---|---|
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.
- The relationship is now defined by the relationship key on either side, not the model name. This has now become the only way to look up the relationship.
- The key on one side of the relationship becomes the other_field on the opposite side, and vice-versa.
- Because we need a way to specify the difference between posts that were edited and those that were created, we have to declare the slightly unusual edited_post and created_post relationships. These could have any name, as long as they were unique and mirrored on Post.
- The table structure is going to be a little different now.
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:
- This shows how you can still have one side of the relationship retain the model name. In this case, $post->post will show the up-stream relationships, while $post->relatedpost shows the downstream.
- This is a Many to Many relationship, so we'll need a dedicated table.
- This is currently a one-way relationship, so each related post will have to be saved inversely as well.
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
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!