DEV Community

Samia Saif
Samia Saif

Posted on

Mastering Morph Relationships in Laravel: A Beginner-Friendly Guide

Have you ever found yourself thinking, “There must be a cleaner, more efficient way to manage relationships between different models in my Laravel app?” If so, you’re in for a treat! Today, we’re going to explore morph relationships in Laravel—a fascinating feature that introduces polymorphism to your database design, making your application both flexible and elegant.


What Are Morph Relationships?

In the Laravel world, morph relationships (or polymorphic relationships) allow one model to be associated with multiple other models using a single, unified relationship. Imagine having a single database table that can dynamically reference multiple types of parent models. Sounds like magic? It almost is!

Instead of creating several foreign keys and additional tables for each relationship, Laravel’s morph relationships let you streamline your database schema. You can think of it as a supercharged, all-in-one connector between your models.


One-to-One Polymorphic Relationships: A Picture-Perfect Example

Let’s start with a simple scenario. Suppose both Users and Posts need to have a profile image. Rather than creating separate image tables for each, you can create one versatile images table that caters to both.

The Setup

Imagine an images table structured like this:

  • id: The unique identifier for each image.
  • url: The path to the image.
  • imageable_id: The ID of the model that the image belongs to.
  • imageable_type: The class name of the owning model (either User or Post).

Here’s what a migration for this table might look like:

Schema::create('images', function (Blueprint $table) {
    $table->id();
    $table->string('url');
    $table->morphs('imageable'); // This creates both imageable_id and imageable_type columns
    $table->timestamps();
});
Enter fullscreen mode Exit fullscreen mode

In Your Models

Next, you define the relationship in your models. In the Image model, you’ll add:

class Image extends Model
{
    public function imageable()
    {
        return $this->morphTo();
    }
}
Enter fullscreen mode Exit fullscreen mode

And in the User and Post models, you’ll use the morphOne method:

class User extends Model
{
    public function image()
    {
        return $this->morphOne(Image::class, 'imageable');
    }
}

class Post extends Model
{
    public function image()
    {
        return $this->morphOne(Image::class, 'imageable');
    }
}
Enter fullscreen mode Exit fullscreen mode

How It Works in Practice

Imagine attaching an image to a user:

$user = User::find(1);
$image = new Image(['url' => 'user-profile.jpg']);
$user->image()->save($image);
Enter fullscreen mode Exit fullscreen mode

Or to a post:

$post = Post::find(1);
$image = new Image(['url' => 'post-banner.jpg']);
$post->image()->save($image);
Enter fullscreen mode Exit fullscreen mode

In both cases, Laravel handles the magic by storing the correct imageable_id and imageable_type values. Simple, yet powerful!


One-to-Many Polymorphic Relationships: Comments on the Go

Now, let’s take it a notch higher. What if you want both Posts and Videos to have multiple Comments? Instead of creating separate comment tables, you can use a one-to-many polymorphic relationship.

The Database Design

Your comments table would include:

  • id: The unique comment identifier.
  • body: The text of the comment.
  • commentable_id: The ID of the parent model.
  • commentable_type: The class name of the parent model (like Post or Video).

A migration might look like this:

Schema::create('comments', function (Blueprint $table) {
    $table->id();
    $table->text('body');
    $table->morphs('commentable'); // Creates commentable_id and commentable_type columns
    $table->timestamps();
});
Enter fullscreen mode Exit fullscreen mode

In Your Models

Define the relationship in the Comment model:

class Comment extends Model
{
    public function commentable()
    {
        return $this->morphTo();
    }
}
Enter fullscreen mode Exit fullscreen mode

And in your Post and Video models:

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');
    }
}
Enter fullscreen mode Exit fullscreen mode

Bringing It to Life

Adding a comment to a post is as easy as:

$post = Post::find(1);
$post->comments()->create(['body' => 'Amazing article!']);
Enter fullscreen mode Exit fullscreen mode

And for a video:

$video = Video::find(1);
$video->comments()->create(['body' => 'Great visuals!']);
Enter fullscreen mode Exit fullscreen mode

Laravel takes care of the details, linking each comment back to its parent model regardless of whether it’s a post or a video.


Many-to-Many Polymorphic Relationships: Tagging for Flexibility

Let’s venture into the realm of many-to-many relationships. Consider a scenario where you have Tags that can be attached to both Posts and Videos. Instead of multiple pivot tables, you can have a single, unified pivot table.

The Schema

You’d have a tags table for storing the tags and a pivot table, typically called taggables, with these columns:

  • tag_id: The ID of the tag.
  • taggable_id: The ID of the parent model.
  • taggable_type: The class name of the parent model.

Here’s a quick look at what the migration might involve:

// Migration for tags table
Schema::create('tags', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->timestamps();
});

// Migration for taggables pivot table
Schema::create('taggables', function (Blueprint $table) {
    $table->id();
    $table->foreignId('tag_id')->constrained()->onDelete('cascade');
    $table->morphs('taggable'); // Creates taggable_id and taggable_type
    $table->timestamps();
});
Enter fullscreen mode Exit fullscreen mode

In Your Models

In the Tag model, define relationships to your different models:

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

    public function videos()
    {
        return $this->morphedByMany(Video::class, 'taggable');
    }
}
Enter fullscreen mode Exit fullscreen mode

And in the Post and Video models, define the relationship:

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

class Video extends Model
{
    public function tags()
    {
        return $this->morphToMany(Tag::class, 'taggable');
    }
}
Enter fullscreen mode Exit fullscreen mode

Making It Work

Attaching a tag to a post could look like this:

$post = Post::find(1);
$tag = Tag::firstOrCreate(['name' => 'Laravel']);
$post->tags()->attach($tag->id);
Enter fullscreen mode Exit fullscreen mode

And the same tag can easily be attached to a video:

$video = Video::find(1);
$video->tags()->attach($tag->id);
Enter fullscreen mode Exit fullscreen mode

This setup provides an incredibly flexible tagging system that can be extended to any model you wish to associate with tags in the future.


Wrapping Up

Laravel’s morph relationships unlock a new level of flexibility for your application’s data structure. By embracing polymorphism, you avoid unnecessary repetition in your database schema and simplify your codebase. When I came across this it was hard to grab at first that their can be polymorphism in database but was stunned to see how Laravel is so cool to have this ability.

So next time you’re faced with a complex data relationship problem, remember: with Laravel’s polymorphic relationships, you have a powerful tool at your disposal. Dive in, experiment, and see how it can transform the way you build your applications!

Happy coding!

Top comments (6)

Collapse
 
shahzi113awan profile image
shahzaib manzoor

Comprehensive, Thanks For this Article, keep it going.

Collapse
 
samia-saif profile image
Samia Saif

means a lot 😊

Collapse
 
shehwar profile image
Shehwar Ahmad

Another good article, Thanks!

Collapse
 
samia-saif profile image
Samia Saif

My pleasure! ☺️

Collapse
 
muhammad_zulqarnainakram profile image
Muhammad Zulqarnain Akram

Very helpful.

Collapse
 
samia-saif profile image
Samia Saif

Thank You!