Laravel Eloquent relations — MorphTo

Jonathan van Rij
4 min readDec 19, 2022

--

This article is the first follow-up to my first article about Laravel’s Eloquent relations. I’ll be diving into the polymorphic relationship MorphTo, MorphOne, and MorphMany.

A polymorphic relationship allows the child model to belong to more than one type of model using a single association. — Laravel docs

In my first article ‘Laravel Eloquent relations — The basics’ I wrote about the (I guess) most commonly used relations. These are the HasOne, HasMany, and ManyToMany. The article started with some basics about the naming conventions used by Laravel.

If you are not familiar with the naming conventions you better first read that part before continuing with this article.

When I first read the documentation about the Eloquent MorphTo relation I was convinced that I would not use it a lot. But it didn’t take long before I found myself in situations where it was really the best solution to the problem I was facing.

In this article, I’ll work with the following models and relations as examples.

These models and relations are used as examples in this article.

Database

Let’s start with the database changes we need to make to get these relations to work. Only the images table needs to undergo some changes because this table has the MorphTo relation.

Just like the BelongsTo relation, the MorphTo relation sits on the model/table that does the pointing ( normally would have the foreign key).

For this to work the images table would need 2 fields. One to store the id of the related model (just like BelongsTo does), and one to store the type of the model.

According to the naming convention, a MorphTo relation uses a base name for the relation that you can determine, and Eloquent will assume several names based on that base name combined with some postfixes. In this example the base name I use is image. And the fields that Eloquent needs on the images table are:

images.imageable_id => contains the id of the model the image relates to
images.imageable_type => contains the class of the related model

As you can see, Eloquent assumes the postfixes able_id and able_type.

Image model

Eloquent will assume that the Image model will have a MorphTo relation defined named ‘image’ + ‘able’. Just like a BelongsTo relation you can call it and it will return the relation, or if you call the attribute, it will return a Post or a User model.

public function imageable(): MorphTo
{
return $this->morphTo();
}
$image->imageable; // Gives a User or a Post model.

MorphOne & MorphMany

Just like the BelongsTo relation type can work with HasOne and HasMany, the MorphTo can work with MorphOne and MorphMany.

An image belongs to one User, because a user can only have one profile picture. Image will use the relation MorphOne.

A post can have multiple images and thus uses the relation MorphMany. Multiple images will be able to point to the same Post.

User model - MorphOne Image

With the following relation, you will be able to get one image / profile-picture of the user.

public function image()
{
// You need to define the model class and the relation name.
return $this->morphOne(Image:class, 'imageable');
}
$image->image; // Gives an Image model

Post model — MorphMany images

With the following relation, you will be able to get all the images that relate to the post

public function images()
{
// You need to define the model class and the relation name.
return $this->morphMany(Image:class, 'imageable');
}
$image->images; // Collection with Image models

Connecting and disconnecting the models

Connecting and disconnecting the users and posts from the image model can be done with the associate and dissociate functions.

$image->imageable()->associate($user);
$image->save();

$image->imageable()->associate($post);
$image->save();

$image->imageable()->dissociate();
$image->save();

If you want to save an image onto a user or a post you can call the save function on the specific model’s relation.

$user->image()->save($image);
$post->images()->save($image);

Full code examples

// Migration class for the images table
return new class extends Migration
{
public function up()
{
Schema::create('images', function (Blueprint $table) {
$table->id();

$table->integer('imageable_id');
$table->string('imageable_type');
// OR use morphs as a shortcut
// $table->morphs('imageable');

$table->timestamps();
});
}

public function down()
{
Schema::dropIfExists('images');
}
};
// Image model
class Image extends Model {

public function imageable(): MorphTo
{
return $this->morphTo();
}

public function connectWithUser(User $user)
{
$this->imageable()->associate($user);
$this->save();
}

public function connectWithPost(Post $post)
{
$this->imageable()->associate($post);
$this->save();
}

public function disconnectFromUserOrPost()
{
$this->imageable()->dissociate();
$this->save();
}

public function getRelatedModel()
{
return $image->imagable; // Gives a User model or a Post model.
}
}
// User model
class User extends Model {

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

public function setProfileImage(Image $image)
{
$this->image()->save($image);
}

public function removeProfileImage(Image $image)
{
$image->imageable()->dissociate();
$image->save();
}

public function getImage()
{
return $image->image; // Gives an Image model
}
}
// Post model
class Post extends Model {

public function images()
{
return $this->morphMany(Image:class, 'imageable');
}

public function addPostImage(Image $image)
{
$this->images()->save($image);
}

public function removeImage(Image $image)
{
$image->imageable()->dissociate();
$image->save();
}

public function exampleCode()
{
return $this->images; // Collection with Image models
}
}

I hope this article helps out! Next one will be about the MorphToMany relation.

--

--

Jonathan van Rij
Jonathan van Rij

Written by Jonathan van Rij

Freelance web developer @ Blijnder. Passionate developing applications in Laravel and Statamic CMS.

No responses yet