Relationships
DataMapper makes it incredibly easy to define and work with database relationships using an elegant, intuitive syntax.
Overview
Define relationships once in your model, then access related data naturally:
php
class User extends DataMapper {
public $has_many = ['post', 'comment'];
public $has_one = ['profile'];
}
// Access relationships
$user = (new User())->find(1);
// Has one
echo $user->profile->bio;
// Has many
foreach ($user->post as $post) {
echo $post->title;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Relationship Types
DataMapper supports all common relationship types:
One-to-One (Has One)
A user has one profile:
php
class User extends DataMapper {
public $has_one = ['profile'];
}
class Profile extends DataMapper {
public $has_one = ['user'];
}1
2
3
4
5
6
7
2
3
4
5
6
7
One-to-Many (Has Many)
A user has many posts:
php
class User extends DataMapper {
public $has_many = ['post'];
}
class Post extends DataMapper {
public $has_one = ['user'];
}1
2
3
4
5
6
7
2
3
4
5
6
7
Many-to-Many
A post has many tags, and tags belong to many posts:
php
class Post extends DataMapper {
public $has_many = ['tag'];
}
class Tag extends DataMapper {
public $has_many = ['post'];
}1
2
3
4
5
6
7
2
3
4
5
6
7
Quick Examples
Accessing Related Data
php
// Get user with ID 1
$user = (new User())->find(1);
// Access related profile (has_one)
echo $user->profile->bio;
// Access related posts (has_many)
foreach ($user->post as $post) {
echo $post->title;
}
// Count related posts
echo $user->post->count();1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
Creating Relationships
php
$user = (new User())->find(1);
$post = new Post();
$post->title = 'My First Post';
$post->content = 'Hello World!';
// Save and associate with user
$post->save($user);1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Querying Relationships
php
// Get user's published posts
$user = (new User())->find(1);
$user->post->where('published', 1)->get();
foreach ($user->post as $post) {
echo $post->title;
}1
2
3
4
5
6
7
2
3
4
5
6
7
DataMapper 2.0: Eager Loading
Eliminate N+1 query problems with eager loading:
Without Eager Loading (N+1 Problem)
php
// 1 query to get users
$users = (new User())->get();
foreach ($users as $user) {
// +1 query per user to get posts!
foreach ($user->post as $post) {
echo $post->title;
}
}
// Total: 1 + N queries (bad for performance)1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
With Eager Loading (2 Queries)
php
// Load users with their posts in 2 queries
$users = (new User())
->with('post')
->get();
foreach ($users as $user) {
// Posts already loaded!
foreach ($user->post as $post) {
echo $post->title;
}
}
// Total: 2 queries (excellent performance)1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
Eager Loading with Constraints
php
// Load users with only their published posts
$users = (new User())
->with([
'post' => function($q) {
$q->where('published', 1)
->order_by('created_at', 'DESC')
->limit(5);
}
])
->get();1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
Nested Eager Loading
php
// Load users -> posts -> comments
$users = (new User())
->with([
'post' => [
'comment' // Load comments for each post
]
])
->get();
foreach ($users as $user) {
foreach ($user->post as $post) {
foreach ($post->comment as $comment) {
echo $comment->content;
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Naming Conventions
DataMapper uses sensible naming conventions:
Table Names
- Model:
User→ Table:users - Model:
Post→ Table:posts - Model:
Category→ Table:categories
Foreign Keys
- User has many posts →
posts.user_id - Post belongs to user →
posts.user_id
Join Tables (Many-to-Many)
- Post has many tags →
posts_tags - Format:
{table1}_{table2}(alphabetical order) - Columns:
post_id,tag_id
Real-World Example
Blog System
php
class User extends DataMapper {
public $has_many = ['post', 'comment'];
public $has_one = ['profile'];
}
class Post extends DataMapper {
public $has_one = ['user', 'category'];
public $has_many = ['comment', 'tag'];
}
class Comment extends DataMapper {
public $has_one = ['user', 'post'];
}
class Tag extends DataMapper {
public $has_many = ['post'];
}
class Category extends DataMapper {
public $has_many = ['post'];
}
class Profile extends DataMapper {
public $has_one = ['user'];
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Usage
php
// Get post with all related data
$post = (new Post())
->with([
'user' => ['profile'],
'category',
'tag',
'comment' => function($q) {
$q->where('approved', 1)
->order_by('created_at', 'DESC');
}
])
->find(1);
// Display
echo $post->title;
echo $post->user->username;
echo $post->user->profile->avatar;
echo $post->category->name;
foreach ($post->tag as $tag) {
echo $tag->name;
}
foreach ($post->comment as $comment) {
echo $comment->user->username . ': ' . $comment->content;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Performance Comparison
Before (N+1 Queries)
php
$users = (new User())->get(); // 1 query
foreach ($users as $user) {
echo $user->username;
$user->post->get(); // +1 query per user
foreach ($user->post as $post) {
echo $post->title;
$post->comment->get(); // +1 query per post
foreach ($post->comment as $comment) {
echo $comment->content;
}
}
}
// Total: 1 + 100 users + (100 users × 10 posts) = 1,101 queries!1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
After (Eager Loading)
php
$users = (new User())
->with([
'post' => ['comment']
])
->get();
foreach ($users as $user) {
echo $user->username;
foreach ($user->post as $post) {
echo $post->title;
foreach ($post->comment as $comment) {
echo $comment->content;
}
}
}
// Total: 3 queries (99.7% reduction!)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Learn More
Dive deeper into relationships:
Best Practices
Define Both Sides
Always define relationships on both sides for clarity:
php
class User extends DataMapper {
public $has_many = ['post'];
}
class Post extends DataMapper {
public $has_one = ['user'];
}1
2
3
4
5
6
7
2
3
4
5
6
7
Use Eager Loading
Always use eager loading when accessing relationships in loops to avoid N+1 queries.
Naming Consistency
Follow DataMapper naming conventions for automatic detection of table and column names.
Next Steps
- Relationship Types - Detailed guide to all types
- Eager Loading - Optimize performance
- Advanced Usage - Complex relationships