Home >Backend Development >PHP Tutorial >Re-Introducing Eloquent's Polymorphic Relationships

Re-Introducing Eloquent's Polymorphic Relationships

Lisa Kudrow
Lisa KudrowOriginal
2025-02-09 12:17:13590browse

Re-Introducing Eloquent's Polymorphic Relationships

Core points

  • Laravel's polymorphic association allows a model to belong to multiple other models on one association. This simplifies the database structure, makes the code easier to maintain, and allows for more dynamic and flexible data relationships.
  • Setting up polymorphic associations in Laravel involves defining associations in the Eloquent model. The morphTo method is used to receive a polymorphic associated model, while the morphMany or morphOne method is used to a model associated with other models.
  • Laravel's MorphMap method can be used to instruct Eloquent to use a custom name instead of a class name for each model. This is helpful in case of model namespace changes or namespaces that are too long.
  • Laravel supports many-to-many polymorphic relationships, allowing a model to belong to multiple models on a many-to-many basis. This is especially useful in complex applications.

This article was peer-reviewed by Younes Rafie. Thanks to all the peer reviewers of SitePoint to make the content of SitePoint perfect!


Re-Introducing Eloquent's Polymorphic Relationships

You may have used different types of relationships between models or database tables, such as common in Laravel: one-to-one, one-to-many, many-to-many, and has-many-through. But there is another less common type: polymorphic association. So what is polymorphic association?

Polymorphic association refers to a model that can belong to multiple other models in one association.

To illustrate this, let's create a fictional scene where we have Topic and Post models. Users can leave comments in topics and posts. Using polymorphic relationships, we can use a single comments table for both cases. Surprisingly, right? This seems a bit impractical because ideally we have to create the post_comments table and the topic_comments table to distinguish comments. Using polymorphic relationships, we do not need two tables. Let's understand polymorphic relationships through a practical example.

What we will build

We will create a demo music app that contains songs and albums. In this app, we can like songs and albums. Using polymorphic relationships, we will use a single upvotes table for both cases. First, let's examine the table structure required to build this relationship:

<code>albums
    id - integer
    name - string

songs
    id - integer
    title - string
    album_id - integer

upvotes
    id - integer
    upvoteable_id - integer
    upvoteable_type - string
</code>

Let's discuss the upvoteable_id and upvoteable_type columns, which may seem a little strange to those who have not used polymorphic relationships before. The upvoteable_id column will contain the ID value of the album or song, while the upvoteable_type column will contain the class name that owns the model. The upvoteable_type column is how the ORM determines which "type" to return to own the model when accessing the upvoteable relationship.

Generate models and migration

I assume you already have a running Laravel application. If not, this advanced quick-start course may be helpful. Let's first create three models and migrations, and then edit the migrations to suit our needs.

<code>albums
    id - integer
    name - string

songs
    id - integer
    title - string
    album_id - integer

upvotes
    id - integer
    upvoteable_id - integer
    upvoteable_type - string
</code>

Note that passing the -m flag when creating a model will also generate migrations associated with these models. Let's adjust the up methods in these migrations to get the desired table structure:

{some_timestamp}_create_albums_table.php

<code>php artisan make:model Album -m
php artisan make:model Song -m
php artisan make:model Upvote -m
</code>

{some_timestamp}_create_songs_table.php

<code class="language-php">public function up()
{
    Schema::create('albums', function (Blueprint $table) {
       $table->increments('id');
        $table->string('name');
        $table->timestamps();
    });
}</code>

{some_timestamp}_create_upvotes_table.php

<code class="language-php">public function up()
{
    Schema::create('songs', function (Blueprint $table) {
        $table->increments('id');
        $table->string('title');
        $table->integer('album_id')->unsigned()->index();
        $table->timestamps();

        $table->foreign('album_id')->references('id')->on('albums')->onDelete('cascade');
    });
}</code>

Now, we can run the artisan migrate command to create these three tables:

<code class="language-php">public function up()
{
    Schema::create('upvotes', function (Blueprint $table) {
        $table->increments('id');
        $table->morphs('upvoteable'); // 添加无符号整数 upvoteable_id 和字符串 upvoteable_type
        $table->timestamps();
    });
}</code>

Let's configure our model to pay attention to the polymorphic relationship between albums, songs and likes:

app/Upvote.php

<code>php artisan migrate</code>

app/Album.php

<code class="language-php">[...]
class Upvote extends Model
{
    /**
     * 获取所有拥有模型。
     */
    public function upvoteable()
    {
        return $this->morphTo();
    }
}</code>

app/Song.php

<code class="language-php">class Album extends Model
{
    protected $fillable = ['name'];

    public function songs()
    {
        return $this->hasMany(Song::class);
    }

    public function upvotes()
    {
        return $this->morphMany(Upvote::class, 'upvoteable');
    }
}</code>
The

method in the upvotes Album and Song models defines the polymorphic one-to-many relationship between these models and the Upvote model and will help us get all the likes of that particular model instance.

After defining the relationship, we can now try the application to better understand how polymorphic relationships work. We won't create any views for this app, we will only try our app from the console.

If you consider the controller and where we should place the like method, I suggest creating an AlbumUpvoteController and a SongUpvoteController. In this way, when we deal with polymorphic relationships, we can strictly connect things to the object we are operating on. In our case, we can like the album and song. Likes are not an album or a song. Also, it's not a general like, which is the opposite of how we have UpvotesController in most one-to-many relationships. Hope this makes sense.

Let's start the console:

<code class="language-php">class Song extends Model
{
    protected $fillable = ['title', 'album_id'];

    public function album()
    {
        return $this->belongsTo(Album::class);
    }

    public function upvotes()
    {
        return $this->morphMany(Upvote::class, 'upvoteable');
    }
}</code>

Retrieval Relationship

Now that we have some data ready, we can access our relationships through our model. Here is a screenshot of the data in the upvotes table:

Re-Introducing Eloquent's Polymorphic Relationships

To access all the likes of the album, we can use upvotes Dynamic attribute:

<code>php artisan tinker
>>> $album = App\Album::create(['name' => 'More Life']);
>>> $song = App\Song::create(['title' => 'Free smoke', 'album_id' => 1]);
>>> $upvote1 = new App\Upvote;
>>> $upvote2 = new App\Upvote;
>>> $upvote3 = new App\Upvote;
>>> $album->upvotes()->save($upvote1);
>>> $song->upvotes()->save($upvote2);
>>> $album->upvotes()->save($upvote3);</code>

can also retrieve the owner of a polymorphic relationship from the polymorphic model by accessing the name of the method that performs the call to morphTo. In our case, that is the upvoteable method on the Upvote model. Therefore, we will access the method as a dynamic property:

<code class="language-php">$album = App\Album::find(1);
$upvotes = $album->upvotes;
$upvotescount = $album->upvotes->count();</code>
The

relation on the upvoteableUpvote model will return an Album instance, as this like is owned by the instance of the Album instance.

Because we can get the number of likes for a song or album, we can sort the song or album based on the likes on the view. That's how music charts work.

For songs, we will get likes like this:

<code>albums
    id - integer
    name - string

songs
    id - integer
    title - string
    album_id - integer

upvotes
    id - integer
    upvoteable_id - integer
    upvoteable_type - string
</code>

Custom polymorphic type

By default, Laravel will use fully qualified class names to store the types of the relevant model. For example, in the above example, Upvote might belong to Album or Song, and the default upvoteable_type is App\Album or App\Song respectively.

However, this has a big drawback. What if the namespace of the Album model changes? We will have to do some sort of migration to rename all occurrences in the upvotes table. This is a little tricky! What happens if the namespace is long (e.g. App\Models\Data\Topics\Something\SomethingElse)? This means we have to set a very long maximum length on the column. This is where the MorphMap method can help us.

The "morphMap" method will instruct Eloquent to use a custom name for each model, not the class name:

<code>php artisan make:model Album -m
php artisan make:model Song -m
php artisan make:model Upvote -m
</code>

We can register morphMap in the boot function of the AppServiceProvider, or create a separate service provider. In order for the new changes to take effect, we must run the composer dump-autoload command. So now we can add this new like record:

<code class="language-php">public function up()
{
    Schema::create('albums', function (Blueprint $table) {
       $table->increments('id');
        $table->string('name');
        $table->timestamps();
    });
}</code>

It behaves exactly the same as the previous example.

Conclusion

Even if you may have never encountered a situation where you need to use polymorphic relationships, that day may eventually come. The advantage of using Laravel is that handling this situation is very easy and it takes no need to do any model association tricks to get things working. Laravel even supports many-to-many polymorphic relationships. You can read more about it here.

I hope you have now understood polymorphic relationships and what may be needed for these types of relationships. Another slightly more advanced example of polymorphic relationships is here. If you find this helpful, please share it with your friends and don't forget to click the Like button. Feel free to leave your thoughts in the comment section below!

FAQs about Eloquent's polymorphic relationships

What are the benefits of using polymorphic relationships in Laravel?

Polymorphic relations in Laravel provide a flexible and efficient way to process related data between different models. They allow a model to belong to multiple other types of models in one association. This means you can have a single unique identifier list of all relevant data regardless of their type. This can greatly simplify your database structure and make your code easier to maintain. It also allows for more dynamic and flexible data relationships, which are particularly useful in complex applications.

How to set polymorphic relationships in Laravel?

Setting up polymorphic relationships in Laravel involves defining relationships in the Eloquent model. First, you need to define the relationship on the model that will receive the polymorphic relationship. This is done using the morphTo method. Then, on the model that will be associated with the other models, use the morphMany or morphOne method according to whether the relationship is one-to-many or one-to-one.

Can you provide an example of polymorphic relationships in Laravel?

Of course, let's consider a blog platform where both posts and users can have comments. In this case, the Comment model will have a polymorphic relationship with the Post and User models. Here is how to define this relationship:

<code>albums
    id - integer
    name - string

songs
    id - integer
    title - string
    album_id - integer

upvotes
    id - integer
    upvoteable_id - integer
    upvoteable_type - string
</code>

How to use polymorphic relationships to retrieve related records?

You can retrieve relevant records in polymorphic relationships like you would with any other Eloquent relationship. For example, if you want to retrieve all comments from a post, you can do this:

<code>php artisan make:model Album -m
php artisan make:model Song -m
php artisan make:model Upvote -m
</code>

How to save related records using polymorphic relationships?

Save related records in polymorphic relationships also similar to other Eloquent relationships. You can associate the model using the associate method and save the model. Here is an example:

<code class="language-php">public function up()
{
    Schema::create('albums', function (Blueprint $table) {
       $table->increments('id');
        $table->string('name');
        $table->timestamps();
    });
}</code>

What are some common use cases for polymorphic relationships?

Polymorphic relations are particularly useful in situations where the model can belong to multiple other types of models. Some common use cases include comments that can belong to posts and users, tags that can be applied to multiple types of content, and images or files that can be attached to various types of entities.

What are the limitations or disadvantages of using polymorphic relationships?

While polymorphic relationships provide a lot of flexibility, they may also be more complex and harder to set up and manage than standard Eloquent relationships. They also support all features of standard relationships, such as desire to load constraints.

How to delete relevant records in polymorphic relationships?

You can use the delete method on the relationship to delete relevant records in a polymorphic relationship. For example, to delete all comments from a post, you can do this:

<code class="language-php">public function up()
{
    Schema::create('songs', function (Blueprint $table) {
        $table->increments('id');
        $table->string('title');
        $table->integer('album_id')->unsigned()->index();
        $table->timestamps();

        $table->foreign('album_id')->references('id')->on('albums')->onDelete('cascade');
    });
}</code>

Can I use polymorphic relationships with many-to-many relationships?

Yes, Laravel supports many-to-many polymorphic relationships through the morphToMany and morphedByMany methods. This allows a model to belong to multiple models on a many-to-many basis.

How to deal with polymorphic relationships in database migration?

In a database migration, you usually add two columns to the table that will receive polymorphic relationships: one for the related model ID and the other for the related model type. Laravel provides a convenient morphs method to add these columns:

<code class="language-php">public function up()
{
    Schema::create('upvotes', function (Blueprint $table) {
        $table->increments('id');
        $table->morphs('upvoteable'); // 添加无符号整数 upvoteable_id 和字符串 upvoteable_type
        $table->timestamps();
    });
}</code>

This adds commentable_id and commentable_type columns to the table.

The above is the detailed content of Re-Introducing Eloquent's Polymorphic Relationships. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn