Eloquent Relationships Mastery

Master all relationship types: hasOne, hasMany, belongsTo, belongsToMany, and polymorphic relationships

Eloquent relationships are the heart of your application's data model. They define how your models relate to each other and provide a fluent, expressive way to work with related data. Understanding when to use each type is crucial for building maintainable applications.

Relationship Types:
• One-to-One (hasOne): User has one Profile
• One-to-Many (hasMany): User has many Posts
• Inverse (belongsTo): Post belongs to User
• Many-to-Many (belongsToMany): User belongs to many Roles
• Has Many Through: Country has many Posts through Users
• Polymorphic: Comment can belong to Post OR Video
• Many-to-Many Polymorphic: Tag can belong to Post, Video, etc.

Real-World Scenario:
Building a social media platform requires understanding all these relationships: Users have Posts, Posts have Comments, Users have many Roles (admin, moderator), and Tags can be attached to Posts, Videos, and Photos (polymorphic).

The Power:
Once defined, relationships let you access related data naturally: $user->posts, $post->comments()->where('approved', true)->get()

Code Examples

One-to-One Relationship
💡 Solution php
<?php
// User has one Profile
class User extends Model
{
    public function profile()
    {
        return $this->hasOne(Profile::class);
        // Convention: profile has user_id column
    }
}

class Profile extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

// Usage:
$user = User::find(1);
$bio = $user->profile->bio;  // Access profile
$username = $user->profile->user->name;  // Back to user

// Creating related records:
$user = User::find(1);
$user->profile()->create([
    'bio' => 'Laravel developer',
    'avatar' => 'avatar.jpg',
]);

// Or:
$profile = new Profile(['bio' => '...']);
$user->profile()->save($profile);

// Real-world example: User settings
class User extends Model
{
    public function settings()
    {
        return $this->hasOne(UserSettings::class);
    }
}

$user->settings->notifications_enabled;
$user->settings->theme;

💡 One-to-One relationships link two models where each record in one table corresponds to exactly one record in another table.

One-to-Many Relationship
💡 Solution php
<?php
// User has many Posts
class User extends Model
{
    public function posts()
    {
        return $this->hasMany(Post::class);
        // Convention: posts have user_id column
    }
}

class Post extends Model
{
    public function author()
    {
        return $this->belongsTo(User::class, 'user_id');
    }
}

// Usage:
$user = User::find(1);
$posts = $user->posts;  // Collection of posts
$count = $user->posts()->count();  // Query builder
$published = $user->posts()->where('published', true)->get();

// Creating related records:
$user = User::find(1);
$user->posts()->create([
    'title' => 'My New Post',
    'content' => '...',
]);

// Or create many:
$user->posts()->createMany([
    ['title' => 'Post 1', 'content' => '...'],
    ['title' => 'Post 2', 'content' => '...'],
]);

// Inverse:
$post = Post::find(1);
$authorName = $post->author->name;

// Querying relationships:
$users = User::has('posts', '>=', 5)->get();  // Users with 5+ posts
$users = User::whereHas('posts', function ($query) {
    $query->where('published', true);
})->get();  // Users with published posts

💡 One-to-Many is the most common relationship. A parent has multiple children, each child belongs to one parent.

Many-to-Many Relationship
💡 Solution php
<?php
// User belongs to many Roles (and vice versa)
// Requires pivot table: role_user (alphabetically ordered)
class User extends Model
{
    public function roles()
    {
        return $this->belongsToMany(Role::class)
                    ->withPivot(['assigned_at', 'assigned_by'])
                    ->withTimestamps();
    }
}

class Role extends Model
{
    public function users()
    {
        return $this->belongsToMany(User::class);
    }
}

// Pivot table migration:
Schema::create('role_user', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained()->onDelete('cascade');
    $table->foreignId('role_id')->constrained()->onDelete('cascade');
    $table->timestamp('assigned_at')->nullable();
    $table->unsignedBigInteger('assigned_by')->nullable();
    $table->timestamps();
});

// Usage:
$user = User::find(1);
$roles = $user->roles;  // All roles for this user
$hasRole = $user->roles->contains('name', 'admin');

// Attaching roles:
$user->roles()->attach($roleId);  // Add role
$user->roles()->attach($roleId, ['assigned_at' => now()]);  // With pivot data

// Detaching:
$user->roles()->detach($roleId);  // Remove specific role
$user->roles()->detach();  // Remove all roles

// Syncing (set exact roles):
$user->roles()->sync([1, 2, 3]);  // User will have only these roles

// Toggle:
$user->roles()->toggle([1, 2]);  // Attach if not attached, detach if attached

// Accessing pivot data:
foreach ($user->roles as $role) {
    echo $role->name;
    echo $role->pivot->assigned_at;  // Pivot table data
    echo $role->pivot->assigned_by;
}

// Querying with pivot conditions:
$user->roles()->wherePivot('assigned_at', '>', now()->subDays(30))->get();

💡 Many-to-Many relationships use a pivot table to link two models where each can have multiple of the other.

Polymorphic Relationships
💡 Solution php
<?php
// Comments can belong to Posts OR Videos
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');
    }
}

// Comments table migration:
Schema::create('comments', function (Blueprint $table) {
    $table->id();
    $table->text('body');
    $table->morphs('commentable');  // Adds commentable_id and commentable_type
    // commentable_type stores: App\Models\Post or App\Models\Video
    // commentable_id stores: the post or video ID
    $table->timestamps();
});

// Usage:
$post = Post::find(1);
$post->comments()->create(['body' => 'Great post!']);

$video = Video::find(1);
$video->comments()->create(['body' => 'Nice video!']);

// Retrieving:
$comment = Comment::find(1);
$parent = $comment->commentable;  // Returns Post or Video instance

if ($parent instanceof Post) {
    echo "Comment on post: " . $parent->title;
} elseif ($parent instanceof Video) {
    echo "Comment on video: " . $parent->title;
}

// Real-world: Tagging system
class Tag extends Model
{
    public function posts()
    {
        return $this->morphedByMany(Post::class, 'taggable');
    }
    
    public function videos()
    {
        return $this->morphedByMany(Video::class, 'taggable');
    }
}

class Post extends Model
{
    public function tags()
    {
        return $this->morphToMany(Tag::class, 'taggable');
    }
}

// Usage:
$tag = Tag::find(1);
$tag->posts;   // All posts with this tag
$tag->videos;  // All videos with this tag

💡 Polymorphic relationships allow a model to belong to multiple other models on a single association. Perfect for features like comments, tags, or likes.