Relationships
Defining and using relationships between Models
Eloquent Relationships allow you to define relationships between database tables in an object-oriented and intuitive way. This feature is one of the most powerful aspects of Eloquent ORM that makes working with related data very simple.
Types of Relationships:
1. hasOne: One to one - one model has one instance of another model (example: User hasOne Profile)
2. hasMany: One to many - one model has many instances of another model (example: User hasMany Post)
3. belongsTo: Many to one - one model belongs to one instance of another model (example: Post belongsTo User)
4. belongsToMany: Many to many - one model has many instances of another model and vice versa (example: User belongsToMany Role)
5. hasManyThrough: Through - one model accesses another model through an intermediate model (example: Country hasManyThrough Post through User)
6. morphTo/morphMany: Polymorphic - one model can belong to multiple different models (example: Comment morphTo Post/Video)
7. hasOneThrough: One to one through - similar to hasManyThrough but for one-to-one
Benefits of Using Relationships:
- <strong>Readability</strong>: More readable and understandable code
- <strong>Type Safety</strong>: Better type hinting and IDE support
- <strong>Eager Loading</strong>: Preventing N+1 query problem
- <strong>Query Building</strong>: Using relationship in query building
- <strong>Cascading Operations</strong>: Cascade operations on related models
- <strong>Database Integrity</strong>: Maintaining referential integrity
Conventions:
Laravel uses naming conventions:
- Foreign key: <code>{model}_id</code> (User → user_id)
- Pivot table: <code>{model1}_{model2}</code> (user_role)
- Polymorphic columns: <code>{model}_id</code>, <code>{model}_type</code>
Relationship Methods vs Properties:
- Relationship method: <code>$user->posts()</code> - returns relationship query builder
- Relationship property: <code>$user->posts</code> - returns collection of related models
Eager Loading:
To prevent N+1 queries, use eager loading:
`php
$users = User::with('posts')->get();
`
Examples
hasMany Relationship
<?php
class User extends Model
{
public function posts()
{
return $this->hasMany(Post::class);
}
}
// Usage
$user = User::find(1);
$posts = $user->posts; // Collection
$postCount = $user->posts()->count(); // Query builder
// Create related model
$user->posts()->create([
'title' => 'New Post',
'content' => 'Post content'
]);
A user can have many posts. Using relationship method and property.
belongsTo Relationship
<?php
class Post extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
// With custom foreign key
public function author()
{
return $this->belongsTo(User::class, 'author_id');
}
}
// Usage
$post = Post::find(1);
$user = $post->user; // User model
// Associate
$post->user()->associate($user);
$post->save();
A post belongs to a user. Foreign key can be customized.
belongsToMany Relationship
<?php
class User extends Model
{
public function roles()
{
return $this->belongsToMany(Role::class, 'user_role')
->withPivot('created_at', 'expires_at')
->withTimestamps();
}
}
class Role extends Model
{
public function users()
{
return $this->belongsToMany(User::class);
}
}
// Usage
$user = User::find(1);
$roles = $user->roles;
// Attach
$user->roles()->attach($roleId, ['expires_at' => now()->addYear()]);
// Sync
$user->roles()->sync([1, 2, 3]);
// Detach
$user->roles()->detach($roleId);
Many to many relationship with pivot table. Pivot columns can be accessed.
hasManyThrough Relationship
<?php
class Country extends Model
{
public function posts()
{
return $this->hasManyThrough(
Post::class,
User::class,
'country_id', // Foreign key on users table
'user_id', // Foreign key on posts table
'id', // Local key on countries table
'id' // Local key on users table
);
}
}
// Usage
$country = Country::find(1);
$posts = $country->posts; // All posts from users in this country
Accessing posts through users. Country → User → Post.
Polymorphic Relationship
<?php
class Comment extends Model
{
public function commentable()
{
return $this->morphTo();
}
}
class Post extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
class Video extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
// Usage
$comment = Comment::find(1);
$commentable = $comment->commentable; // Post or Video
$post = Post::find(1);
$comments = $post->comments;
Polymorphic relationship where one model can belong to multiple different models.
Relationship with Constraints
<?php
class User extends Model
{
public function publishedPosts()
{
return $this->hasMany(Post::class)->where('status', 'published');
}
public function recentPosts()
{
return $this->hasMany(Post::class)
->where('created_at', '>', now()->subDays(7))
->orderBy('created_at', 'desc');
}
}
// Usage
$user = User::find(1);
$publishedPosts = $user->publishedPosts;
$recentPosts = $user->recentPosts;
Defining relationship with constraints to filter results.
Eager Loading with Relationships
<?php
// Eager load single relationship
$users = User::with('posts')->get();
// Eager load multiple relationships
$users = User::with(['posts', 'profile', 'roles'])->get();
// Nested eager loading
$users = User::with('posts.comments')->get();
// Eager load with constraints
$users = User::with(['posts' => function ($query) {
$query->where('published', true);
}])->get();
// Lazy eager loading
$users = User::all();
$users->load('posts');
Using eager loading to prevent N+1 queries.
Use Cases
- Defining relationships between entities in database
- Easy access to related data
- Query building with relationships
- Cascading operations on related models
- Polymorphic associations for flexible data structures
- Many-to-many relationships with pivot tables
- Accessing data through intermediate models
Common Mistakes
- N+1 query problem with lazy loading relationships
- Forgetting eager loading for relationships
- Using relationship property instead of method for query building
- Defining relationship with wrong foreign key
- Forgetting return statement in relationship method
- Using relationship in loop without eager loading
Best Practices
- Always use eager loading for relationships
- Use relationship method for query building
- Use relationship property for accessing data
- Define foreign keys correctly
- Use constraints in relationships
- Define pivot columns with withPivot()
- Keep relationship naming consistent
Edge Cases
- Relationships with custom foreign keys
- Polymorphic relationships with multiple types
- Many-to-many relationships with pivot data
- Relationships with soft deletes
- Circular relationships
- Relationships with multiple database connections
- Relationships with composite keys
Performance Notes
- Eager loading can solve N+1 problem
- Use select() for selecting specific columns in relationships
- Use withCount() for counting relationships
- Use exists() for checking existence
- Use whereHas() for filtering with relationships
- Indexes for foreign keys are important
Security Notes
- Ensure relationships are properly authorized
- Use user input in relationship queries with caution
- Ensure pivot data is validated
- Use mass assignment in relationships with caution
- Ensure polymorphic relationships are secure
Interview Points
- What are the types of relationships in Eloquent?
- What is the difference between hasMany and belongsTo?
- What is N+1 query problem and how is it solved?
- What is polymorphic relationship and when is it used?
- What is the difference between relationship method and property?
- How can you define relationship with constraints?
Version Notes
- Laravel 11.x: Improved performance in relationship queries
- Laravel 11.x: Better support for eager loading
- Laravel 10.x: Improved polymorphic relationships
- Laravel 9.x: Improved relationship constraints