Laravel one to many polymorphic Eloquent relationship tutorial

In a database structure, There are many tables are associated with another tables. For example in a commerce application, a user have many orders and orders have many items.

Laravel provides eloquent relationship which provides powerful query builders. In Laravel, eloquent relationships are defined in model classes. Laravel eloquent provides easy ways to create common relationships:

In this article, we will discuss on Laravel's one to many polymorphic eloquent relationship. This is similar to one-to-many relationship. However, in this relationship, a child model may be associated with more than one type of model. For example, in a media application, a user can comment on Video and Audio both. So, a Comment model may be associated with Video model as well as Audio model.

Example:

Now, in this example, we will build relationship between Comment with Video and Audio model. We assume that you have created fresh Laravel application. We also assume that you have confiured database connection.

Database tables

In this relationship, we have three database tables: comments, videos and audios. A comments table will be associated with videos and audios table.

Migration

We need to create three migration table. Run the following three commands into Terminal to create migration classes at database/migrations directory.

php artisan make:migration create_comments_table
php artisan make:migration create_videos_table
php artisan make:migration create_audios_table

Below are the migration table fields for these table:

videos migration

In the videos migration, we have defined the following fields:

/**
 * Run the migrations.
 *
 * @return void
 */
public function up()
{
    Schema::create('videos', function (Blueprint $table) {
        $table->id();
        $table->string('url');
        $table->timestamps();
    });
}

audios migration

/**
 * Run the migrations.
 *
 * @return void
 */
public function up()
{
    Schema::create('audios', function (Blueprint $table) {
        $table->id();
        $table->string('url');
        $table->timestamps();
    });
}

comments migration

/**
 * Run the migrations.
 *
 * @return void
 */
public function up()
{
    Schema::create('comments', function (Blueprint $table) {
        $table->id();
        $table->text('body');
        $table->integer('commentable_id');
        $table->string('commentable_type');
        $table->timestamps();
    });
}

Now, let's look at the migration fields. We have commentable_id and commentable_type fields in the comments migration. The commentable_id will store id value of video or audio and commentable_type will store the Video or Audio class name like App\Models\Video or App\Models\Audio.

Now run the migrate command to create tables into database.

php artisan migrate

Model

Laravel models are located at app/Models directory. Create the model classes for these tables using following Artisan commands one by one into Terminal.

php artisan make:model Video
php artisan make:model Audio
php artisan make:model Comment

Now let's build relationship. First create commentable method into Comment model which will return morphTo() method. This will retrieve the parent model record.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    use HasFactory;

    /**
     * Get the parent commentable model.
     */
    public function commentable()
    {
        return $this->morphTo();
    }
}

Next, create comments method into Video model.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Video extends Model
{
    use HasFactory;

    protected $table = 'videos';

    /**
     * Get the videos's comments.
     */
    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

Also create comments method into Audio model.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Audio extends Model
{
    use HasFactory;

    protected $table = 'audios';

    /**
     * Get the audio's comments.
     */
    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

You can also use custom field name for "id" and "type" columns of polymorphic model. But you need to ensure that you pass the name of the relationship as the first argument to the morphTo() method. For that, you may use PHP's __FUNCTION__ magic constant. The second and third arguments are name of type and id fields like below:

/**
 * Get the model's comments.
 */
public function comments()
{
    return $this->morphTo(__FUNCTION__, 'commentable_type', 'commentable_id');
}

Route

In routes/web.php file, we have added two new routes for relationship testing.

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\CommentController;

Route::get('/video-comments', [CommentController::class, 'video']);
Route::get('/audio-comments', [CommentController::class, 'audio']);

Controller

As we have added route, also create CommentController using following command.

php artisan make:controller CommentController

Open the controller at app/Http/Controllers/CommentController and create video and audio method. To retrieve the comments of video or audio, you can access comments property of the model object.

<?php

namespace App\Http\Controllers;

use App\Models\Audio;
use App\Models\Comment;
use App\Models\Video;
use Illuminate\Http\Request;

class CommentController extends Controller
{
    /**
     * Access video comments.
     *
     * @return \Illuminate\Http\Response
     */
    public function video()
    {
        $video_comments = Video::find(1)->comments;

        dd($video_comments);
    }

    /**
     * Access audio comments.
     *
     * @return \Illuminate\Http\Response
     */
    public function audio()
    {
        $audio_comments = Audio::find(1)->comments;

        dd($audio_comments);
    }
}

This will return all comments record with related model's id of 1. If the relationship not found, the eloquent will return null record.

In the reverse, you may already have the Comment model and you may want to retrieve the parent model, you may call morphTo() method of Comment model. We have defined commentable() method into Comment model, so we need to access commentable property of Comment model. 

/**
 * Access the parent model of comment.
 *
 * @return \Illuminate\Http\Response
 */
public function index()
{
    $parent_model = Comment::find(2)->commentable;

    dd($parent_model);
}

This will return parent model record which is associated with Comment model.

I hope this will help you to understand one to many polymorphic relationship.