Eloquent & Polymorphic Relations: Overview and Usage Guide

Share this article

While I was working on an application for a client, I had to implement a new module that entails the following:

  • Users ask for a budget quotation for a certain task.
  • Every task has a location.
  • Professionals can subscribe to different zones.
  • A zone can be a region or a city.
Now, let’s neglect the core application and try to implement this single module to see what we can achieve here. laravel-l-slant

This article was peer reviewed by Christopher Vundi and Christopher Pitt. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

Scaffolding Application

I assume you have your development environment already set up. If not, you can check this Homestead Improved Quick Tip or just use the official Homestead Box. Go ahead and create a new Laravel project using the Laravel installer or via Composer.
laravel new demo
Or
composer create-project --prefer-dist laravel/laravel demo
Edit your .env file with your database credentials.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead    
DB_PASSWORD=secret

Creating Migrations

Before creating our migrations, we should talk about Eloquent polymorphic relations and how they can benefit us in this case! Polymorphism is often used when an object can have different forms (shapes). In our case, professional users subscribe to different zones and they get notified whenever a new job is posted in this area. Lets start by creating the zones table.
php artisan make:model Zone --migration
This creates a migration file, but we do need to add a bit of code to it before it’s complete, as demonstrated by:
// database/migrations/2016_12_02_130436_create_zones_table.php

class CreateZonesTable extends Migration
{
    public function up()
    {
        Schema::create('zones', function (Blueprint $table) {
            $table->integer('user_id')->unsigned();

            $table->integer('zone_id')->unsigned();
            $table->string('zone_type');
        });
    }

    public function down()
    {
        Schema::dropIfExists('zones');
    }
}
Next, we create the cities and regions tables.
php artisan make:model Region --migration
// database/migrations/2016_12_02_130701_create_regions_table.php

class CreateRegionsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('regions', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
        });

    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('regions');
    }
}
php artisan make:model City --migration
// database/migrations/2016_12_02_130709_create_cities_table.php

class CreateCitiesTable extends Migration
{
    public function up()
    {
        Schema::create('cities', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name', 255);
            $table->integer('postal_code')->unsigned();

            $table->integer('region_id')->unsigned()->nullable();
        });
    }

    public function down()
    {
        Schema::drop('cities');
    }
}
We could’ve achieved the same result by making a many to many relation with the cities and regions table. However, the zones table will act as an abstract class for the two other tables. The zone_id will hold an id for a city or a region and using the zone_type value Eloquent will decide which model instance to return.

Creating Models

Eloquent has the ability to guess the related table fields, but I decided not to use it just to explain how to map database fields to models. I will explain them as we go along!
// app/User.php

class User extends Authenticatable
{
    // ...

    public function cities()
    {
        return $this->morphedByMany(City::class, 'zone', 'zones', 'user_id', 'zone_id');
    }

    public function regions()
    {
        return $this->morphedByMany(Region::class, 'zone', 'zones', 'user_id', 'zone_id');
    }
}
The morphedByMany method is similar to the manyToMany one. We specify the related model, the mapping field name (used for zone_type and zone_id), related table name, current model foreign key and the morphed relation key. We could’ve automated this by letting Eloquent guess field names, if you take a look at the documentation you’ll see that we can name the fields as zoneable_id and zoneable_type, then only specify the mapped field name (return $this->morphedByMany(City::class, 'zoneable').
// app/Region.php

class Region extends Model
{
    // ...

    public function cities()
    {
        return $this->hasMany(City::class);
    }

    public function users()
    {
        return $this->morphMany(User::class, 'zones', 'zone_type', 'zone_id');
    }
}
We can quickly guess the parameters definition from the above code. We specify the related table name, morphed relation type and ID.
// app/City.php

class City extends Model
{
    // ...

    public function users()
    {
        return $this->morphMany(User::class, 'zones', 'zone_type', 'zone_id');
    }
}
Now that everything is set up, we can start testing our relations and see if everything is working as expected. We should first seed our tables to save some time. You can grab the database seeder’s code from the GitHub repository.

Using Relations

We can attach cities and regions to users using syncing, attaching, and detaching Eloquent methods. Here’s an example:
$user = App\User::find(1);

$user->cities()->sync(App\City::limit(3)->get());
This will attach three cities to the selected user, and we can do the same for regions.
$user = App\User::find(1);

$user->regions()->sync(App\Region::limit(3)->get());
Eloquent Polymorphism relations If we inspect our database now to see what was saved, we can see the following:
mysql> select * from zones;
+---------+---------+------------+
| user_id | zone_id | zone_type  |
+---------+---------+------------+
|       1 |       1 | App\City   |
|       1 |       2 | App\City   |
|       1 |       3 | App\City   |
|       1 |       1 | App\Region |
|       1 |       2 | App\Region |
|       1 |       3 | App\Region |
+---------+---------+------------+
6 rows in set (0.00 sec)
We can also detach regions if they exist.
$user = App\User::find(1);

$user->regions()->detach(App\Region::limit(3)->get());
Now, we can fetch user cities and regions as we would do on a normal many to many relationship:
$user = App\User::find(1);

dd($user->regions, $user->cities);
We could add a new method called zones which will merge cities from regions with individual selected cities.
class User extends Authenticatable
{
    // ...

    public function zones()
    {
        return $this->regions->pluck("cities")->flatten()->merge($this->cities);
    }
}
The pluck method will get a collection of cities from each region, which be then flattened (merge all the collections into one) and merged with the user selected cities. You can read more about collections in the documentation, and if you want to learn more, I recommend this Refactoring to Collections book from Adam Wathan.

Conclusion

Even though polymorphic relations are rarely used in applications, Eloquent makes it’s easy to deal with related tables and returning the correct data. If you have any questions or comments about Eloquent or Laravel in general, you can post them below and I’ll do my best to answer them!

Frequently Asked Questions on Polymorphic Relations and Eloquent

What is the main difference between polymorphic and many-to-many relationships in Laravel?

In Laravel, both polymorphic and many-to-many relationships are types of Eloquent relationships. However, they differ in their functionality. A many-to-many relationship is used when multiple records in one table are associated with multiple records in another table. On the other hand, a polymorphic relationship allows a model to belong to any number of models on a single association. It provides more flexibility as it allows you to have a single list of unique entities that can be used in different contexts.

How can I define a polymorphic relationship in Laravel?

Defining a polymorphic relationship in Laravel involves a few steps. First, you need to define the relationship in the model using the morphTo method. This method will automatically determine the type and id columns. Then, you need to define the inverse of the relationship using the morphMany or morphOne methods. These methods accept three arguments: the related model, the name of the polymorphic type, and the foreign key name.

Can you provide an example of a polymorphic relationship in Laravel?

Sure, let’s consider a scenario where you have three models: Post, Video, and Comment. Both Post and Video models can have comments, but the type of commentable model can be different. In this case, you can use a polymorphic relationship. The Comment model would have a commentable_id and commentable_type field, which will store the ID and class name of the associated model respectively.

What are the benefits of using polymorphic relationships in Laravel?

Polymorphic relationships in Laravel provide a flexible and efficient way to handle related data. They allow a model to belong to more than one other model on a single association. This means you can have a single list of entities that can be used in different contexts, reducing the need for duplicate code. They also make your code more maintainable and easier to understand.

How can I retrieve records using a polymorphic relationship in Laravel?

Retrieving records using a polymorphic relationship in Laravel is straightforward. You can use the name of the method that defines the relationship to retrieve the related records. For example, if you have a Comment model with a polymorphic relationship to a Post model, you can retrieve all comments for a post using the comments method: $post->comments.

How can I delete records using a polymorphic relationship in Laravel?

Deleting records using a polymorphic relationship in Laravel is similar to retrieving records. You can use the name of the method that defines the relationship to delete the related records. For example, if you have a Comment model with a polymorphic relationship to a Post model, you can delete all comments for a post using the comments method: $post->comments()->delete().

Can I use polymorphic relationships with Laravel’s query builder?

Yes, you can use polymorphic relationships with Laravel’s query builder. However, keep in mind that the query builder does not provide any support for retrieving related records. You will need to manually join the related tables and select the appropriate columns.

How can I use polymorphic relationships with Laravel’s Eloquent ORM?

Using polymorphic relationships with Laravel’s Eloquent ORM is straightforward. You can define the relationship in your model using the morphTo, morphMany, or morphOne methods. Then, you can use these methods to retrieve or manipulate the related records.

Can I use polymorphic relationships with Laravel’s collections?

Yes, you can use polymorphic relationships with Laravel’s collections. Collections provide a convenient wrapper for working with arrays of data, and they work seamlessly with Eloquent’s relationships. You can use the pluck, map, filter, and other collection methods to work with the related records.

How can I handle polymorphic relationships in Laravel when dealing with complex data structures?

When dealing with complex data structures, it’s important to carefully plan your database schema and Eloquent relationships. Polymorphic relationships can be a powerful tool in these situations, as they allow a model to belong to more than one other model on a single association. However, they can also add complexity to your code, so it’s important to use them judiciously.

Younes RafieYounes Rafie
View Author

Younes is a freelance web developer, technical writer and a blogger from Morocco. He's worked with JAVA, J2EE, JavaScript, etc., but his language of choice is PHP. You can learn more about him on his website.

BrunoSdatabaseeloquentframeworkFrameworkslaravelOOPHPPHPphp frameworkphp frameworkspolymorphismrelational
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week