Eloquent Relationships
Eloquent Relationships
Eloquent relationships are one of the most powerful features of Laravel. They allow you to define connections between database tables and work with related data using intuitive, object-oriented syntax. In this lesson, you'll master all major relationship types.
Why Use Relationships?
Relationships eliminate the need for complex JOIN queries and make your code more readable and maintainable. Instead of writing SQL, you access related data as object properties.
One to One (hasOne / belongsTo)
A one-to-one relationship is where one record in a table is related to exactly one record in another table. Example: A user has one profile.
Database Structure
// users table
id | name | email | created_at | updated_at
// profiles table
id | user_id | bio | website | avatar | created_at | updated_at
Defining the Relationship
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
// User has one Profile
public function profile()
{
return $this->hasOne(Profile::class);
}
}
class Profile extends Model
{
// Profile belongs to User
public function user()
{
return $this->belongsTo(User::class);
}
}
Using One-to-One Relationships
// Access related data
$user = User::find(1);
$profile = $user->profile; // Returns Profile model
echo $profile->bio;
echo $profile->website;
// Reverse relationship
$profile = Profile::find(1);
$user = $profile->user; // Returns User model
echo $user->name;
// Create related record
$user = User::find(1);
$user->profile()->create([
'bio' => 'Laravel developer',
'website' => 'https://example.com',
'avatar' => 'avatar.jpg',
]);
// Update related record
$user->profile->update([
'bio' => 'Updated bio',
]);
One to Many (hasMany / belongsTo)
A one-to-many relationship is where one record can be associated with multiple related records. Example: A user has many posts.
Database Structure
// users table
id | name | email | created_at | updated_at
// posts table
id | user_id | title | content | published_at | created_at | updated_at
Defining the Relationship
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
// User has many Posts
public function posts()
{
return $this->hasMany(Post::class);
}
}
class Post extends Model
{
protected $fillable = ['title', 'content', 'published_at'];
// Post belongs to User
public function user()
{
return $this->belongsTo(User::class);
}
}
Using One-to-Many Relationships
// Access all posts of a user
$user = User::find(1);
$posts = $user->posts; // Returns Collection of Post models
foreach ($posts as $post) {
echo $post->title;
}
// Query the relationship
$publishedPosts = $user->posts()
->where('published_at', '!=', null)
->orderBy('published_at', 'desc')
->get();
// Count related records
$postCount = $user->posts()->count();
// Check if relationship exists
if ($user->posts()->exists()) {
echo "User has posts";
}
// Create related record
$user->posts()->create([
'title' => 'My New Post',
'content' => 'Post content here...',
'published_at' => now(),
]);
// Save existing model
$post = new Post([
'title' => 'Another Post',
'content' => 'More content...',
]);
$user->posts()->save($post);
// Save multiple models
$user->posts()->saveMany([
new Post(['title' => 'Post 1']),
new Post(['title' => 'Post 2']),
]);
Many to Many (belongsToMany)
A many-to-many relationship is where multiple records in one table are related to multiple records in another table. Example: A post has many tags, and a tag has many posts.
Database Structure
// posts table
id | title | content | created_at | updated_at
// tags table
id | name | slug | created_at | updated_at
// post_tag table (pivot table)
id | post_id | tag_id | created_at | updated_at
post_tag (not tag_post). Foreign keys should be singular: post_id and tag_id.
Defining the Relationship
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
// Post belongs to many Tags
public function tags()
{
return $this->belongsToMany(Tag::class);
}
}
class Tag extends Model
{
protected $fillable = ['name', 'slug'];
// Tag belongs to many Posts
public function posts()
{
return $this->belongsToMany(Post::class);
}
}
Using Many-to-Many Relationships
// Access all tags of a post
$post = Post::find(1);
$tags = $post->tags;
foreach ($tags as $tag) {
echo $tag->name;
}
// Access all posts with a specific tag
$tag = Tag::where('slug', 'laravel')->first();
$posts = $tag->posts;
// Attach tags to a post (add relationships)
$post->tags()->attach([1, 2, 3]); // By IDs
$post->tags()->attach($tag); // By model
// Detach tags (remove relationships)
$post->tags()->detach([1, 2]); // Remove specific tags
$post->tags()->detach(); // Remove all tags
// Sync tags (replace all existing)
$post->tags()->sync([1, 2, 3]);
// Sync without detaching
$post->tags()->syncWithoutDetaching([4, 5]);
// Toggle tags (attach if not attached, detach if attached)
$post->tags()->toggle([1, 2, 3]);
Pivot Table Data
Access and store additional data on the pivot table:
<?php
// Define pivot columns in relationship
class Post extends Model
{
public function tags()
{
return $this->belongsToMany(Tag::class)
->withPivot('featured', 'order')
->withTimestamps();
}
}
// Access pivot data
$post = Post::find(1);
foreach ($post->tags as $tag) {
echo $tag->name;
echo $tag->pivot->featured;
echo $tag->pivot->order;
echo $tag->pivot->created_at;
}
// Attach with pivot data
$post->tags()->attach(1, [
'featured' => true,
'order' => 1
]);
// Sync with pivot data
$post->tags()->sync([
1 => ['featured' => true, 'order' => 1],
2 => ['featured' => false, 'order' => 2],
]);
Has Many Through
This relationship provides a shortcut for accessing distant relations through an intermediate relation. Example: A country has many posts through users.
Database Structure
// countries table
id | name
// users table
id | country_id | name
// posts table
id | user_id | title | content
Defining the Relationship
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Country extends Model
{
// Country has many Posts through Users
public function posts()
{
return $this->hasManyThrough(
Post::class, // Final model
User::class, // Intermediate model
'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
);
}
}
Using Has Many Through
$country = Country::find(1);
$posts = $country->posts; // All posts by users in this country
foreach ($posts as $post) {
echo $post->title;
}
Polymorphic Relationships
Polymorphic relationships allow a model to belong to more than one other model on a single association. Example: Comments can belong to both posts and videos.
Database Structure
// posts table
id | title | content
// videos table
id | title | url
// comments table
id | body | commentable_id | commentable_type | created_at
commentable_type column stores the model class name (e.g., "App\Models\Post"), and commentable_id stores the related model's ID.
Defining Polymorphic Relationships
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
protected $fillable = ['body'];
// Comment belongs to commentable (Post or Video)
public function commentable()
{
return $this->morphTo();
}
}
class Post extends Model
{
// Post has many Comments (polymorphic)
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
class Video extends Model
{
// Video has many Comments (polymorphic)
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
Using Polymorphic Relationships
// Create comments for a post
$post = Post::find(1);
$post->comments()->create([
'body' => 'Great post!'
]);
// Create comments for a video
$video = Video::find(1);
$video->comments()->create([
'body' => 'Nice video!'
]);
// Retrieve all comments
$post->comments; // Comments for post
$video->comments; // Comments for video
// Reverse relationship (get parent)
$comment = Comment::find(1);
$commentable = $comment->commentable; // Returns Post or Video
if ($commentable instanceof Post) {
echo "Comment on post: " . $commentable->title;
} elseif ($commentable instanceof Video) {
echo "Comment on video: " . $commentable->title;
}
Eager Loading
Eager loading solves the N+1 query problem by loading relationships in advance, dramatically improving performance.
The N+1 Problem
// BAD: N+1 queries (1 + 100 = 101 queries for 100 users)
$users = User::all(); // 1 query
foreach ($users as $user) {
echo $user->posts->count(); // 100 additional queries!
}
Solution: Eager Loading
// GOOD: Eager loading (only 2 queries)
$users = User::with('posts')->get(); // 2 queries total
foreach ($users as $user) {
echo $user->posts->count(); // No additional queries!
}
// Eager load multiple relationships
$posts = Post::with(['user', 'tags', 'comments'])->get();
// Nested eager loading
$countries = Country::with('users.posts')->get();
// Conditional eager loading
$users = User::with([
'posts' => function ($query) {
$query->where('published_at', '!=', null)
->orderBy('published_at', 'desc')
->limit(5);
}
])->get();
// Eager load specific columns
$users = User::with('posts:id,user_id,title')->get();
Lazy Eager Loading
$users = User::all();
// Load relationship after retrieving models
if ($someCondition) {
$users->load('posts');
}
// Load with constraints
$users->load([
'posts' => function ($query) {
$query->where('published', true);
}
]);
Querying Relationships
// Has: Get users who have at least one post
$users = User::has('posts')->get();
// Has with count
$users = User::has('posts', '>=', 3)->get();
// WhereHas: Get users with published posts
$users = User::whereHas('posts', function ($query) {
$query->where('published_at', '!=', null);
})->get();
// DoesntHave: Get users without posts
$users = User::doesntHave('posts')->get();
// WhereDoesntHave: Get users without published posts
$users = User::whereDoesntHave('posts', function ($query) {
$query->where('published_at', '!=', null);
})->get();
// Count related records
$users = User::withCount('posts')->get();
foreach ($users as $user) {
echo $user->posts_count; // posts_count attribute added
}
Create a complete blog relationship structure:
- User hasMany Posts
- Post belongsTo User
- Post hasMany Comments
- Comment belongsTo Post and User
- Post belongsToMany Tags
- Create migrations for all tables including pivot table
- Define all relationships in models
- Test creating posts with tags and comments
Implement a polymorphic relationship for images:
- Create an Image model that can belong to Users (avatar) and Posts (featured image)
- Create migration with
imageable_idandimageable_type - Define
morphManyin User and Post models - Define
morphToin Image model - Test attaching images to users and posts
- Query all posts with their images using eager loading
Given this code with N+1 query problem:
$posts = Post::all();
foreach ($posts as $post) {
echo $post->user->name;
echo $post->comments->count();
foreach ($post->tags as $tag) {
echo $tag->name;
}
}
Rewrite it using eager loading to execute only 4 queries total.
Best Practices
- Always Use Eager Loading: When accessing relationships in loops, always eager load to avoid N+1 queries.
- Name Relationships Clearly: Use singular for
belongsTo, plural forhasManyandbelongsToMany. - Use Query Scopes: Combine relationships with query scopes for cleaner code.
- Leverage Relationship Methods: Use relationship methods like
create(),save(), andattach()instead of manual foreign key assignment. - Monitor Query Count: Use Laravel Debugbar to monitor query count and identify N+1 problems.
- Cascade Deletes: Set up proper foreign key constraints with
onDelete('cascade')in migrations.
Summary
In this lesson, you've mastered:
- One-to-one relationships (hasOne / belongsTo)
- One-to-many relationships (hasMany / belongsTo)
- Many-to-many relationships with pivot tables (belongsToMany)
- Has many through relationships for distant relations
- Polymorphic relationships for flexible associations
- Eager loading to eliminate N+1 query problems
- Querying relationships with has, whereHas, and withCount
Eloquent relationships transform complex database interactions into elegant, readable code. Master them, and you'll build powerful, efficient applications with ease!