How to Create Comment Nesting in Laravel

To create a comment nesting in Laravel, use the “Polymorphic relationship.” A one-to-one polymorphic relationship is a case where one model can belong to more than one type of model but on only one association. For example, multiple comments belong to multiple users, and a comment can have multiple replies.

Here are the steps to create comment nesting in Laravel:

Step 1: Install and configure Laravel.

laravel new comments

# or

composer create-project laravel/laravel comments --prefer-dist

Go to the project.

cd comments

Open the project in your editor.

code .

Configure the MySQL database in the .env file.

Create an auth using the following command.

php artisan make:auth

Now migrate the database using the following command.

php artisan migrate

Step 2: Create a model and migration.

Create a Post model and migration using the following command.

php artisan make:model Post -m

Define the schema in the post-migration file.

// create_posts_table

public function up()
{
    Schema::create('posts', function (Blueprint $table) {
        $table->increments('id');
        $table->string('title');
        $table->text('body');
        $table->timestamps();
    });
}

Also, we need to create a Comment model and migration, so create by using the following command.

php artisan make:model Comment -m

We will use the Polymorphic relationship between the models. So we need to define the schema that way.

// create_comments_table

public function up()
{
    Schema::create('comments', function (Blueprint $table) {
       $table->increments('id');
       $table->integer('user_id')->unsigned();
       $table->integer('parent_id')->unsigned();
       $table->text('body');
       $table->integer('commentable_id')->unsigned();
       $table->string('commentable_type');
       $table->timestamps();
    });
}

Migrate the database using the following cmd.

php artisan migrate

How To Create Comment Nesting In Laravel From Scratch

Step 3: Define Polymorphic Relationships.

We need to define the Polymorphic relationships between the models. So write the following code inside the app >> Post.php file. 

<?php

// Post.php 

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable')->whereNull('parent_id');
    }
}

Here, we have written all the comments whose parent_id is null. The reason is that we need to display the parent-level comment and also save the parent-level comment. That is why. We need to differentiate between the Comment and the replies.

Post also belongs To a User. So we can define that relationship as well.

<?php

// Post.php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable')->whereNull('parent_id');
    }
}

Define the Comment’s relationship with the Post. For example, write the following code inside the Comment.php file.

<?php

// Comment.php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

Step 3: Define the views, controller, and routes.

Create a PostController.php file using the following command.

php artisan make:controller PostController

The next step is defining the view route and storing the post in the database. For example, write the following code inside routes >> web.php file.

<?php

// web.php

Route::get('/', function () {
    return view('welcome');
});

Auth::routes();

Route::get('/home', 'HomeController@index')->name('home');

Route::get('/post/create', 'PostController@create')->name('post.create');
Route::post('/post/store', 'PostController@store')->name('post.store');

Write the following code inside the PostController.php file.

<?php

// PostController.php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PostController extends Controller
{

    public function __construct()
    {
        return $this->middleware('auth');
    }

    public function create()
    {
        return view('post');
    }

    public function store(Request $request)
    {
        // store code
    }
}

We need to create a form for creating the post. So create a blade file inside the resources >> views folder called post.blade.php. Write the following code inside a post.blade.php file.

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">Create Post</div>
                <div class="card-body">
                    <form method="post" action="{{ route('post.store') }}">
                        <div class="form-group">
                            @csrf
                            <label class="label">Post Title: </label>
                            <input type="text" name="title" class="form-control" required/>
                        </div>
                        <div class="form-group">
                            <label class="label">Post Body: </label>
                            <textarea name="body" rows="10" cols="30" class="form-control" required></textarea>
                        </div>
                        <div class="form-group">
                            <input type="submit" class="btn btn-success" />
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Go to the resources >> views >> layouts >> app.blade.php file and add a link to create a post.

We must add the link to the @else part of the navigation bar. So, if the user is successfully logged in, they can create a post; otherwise, they cannot.

@else
     <li class="nav-item">
          <a class="nav-link" href="{{ route('post.create') }}">Create Post</a>
     </li>

Go to this link: http://comments.test/register and register a user. After logging in, you can see the Create Post in the navbar. Click that item, and you will redirect to this route: http://comments.test/post/create. Next, you can visit our form with the title and body form fields.

Comment Nesting in Laravel 5.6

Step 4: Save and display the Post.

We need to save the post in the database, so write the following code inside the store function of the PostController.php file.

<?php

// PostController.php

namespace App\Http\Controllers;
use App\Post;

use Illuminate\Http\Request;

class PostController extends Controller
{

    public function __construct()
    {
        return $this->middleware('auth');
    }

    public function create()
    {
        return view('post');
    }

    public function store(Request $request)
    {
        $post =  new Post;
        $post->title = $request->get('title');
        $post->body = $request->get('body');

        $post->save();

        return redirect('posts');

    }
}

After saving the post, we are redirecting to the posts list page. We need to define its route too. Add the following route inside a web.php file.

// web.php

Route::get('/posts', 'PostController@index')->name('posts');

Also, we need to define the index function inside the PostController.php file.

// PostController.php

public function index()
{
    $posts = Post::all();

    return view('index', compact('posts'));
}

Create an index.blade.php file inside the views folder. For example, write the following code inside an index.blade.php file.

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <table class="table table-striped">
                <thead>
                    <th>ID</th>
                    <th>Title</th>
                    <th>Action</th>
                </thead>
                <tbody>
                @foreach($posts as $post)
                <tr>
                    <td>{{ $post->id }}</td>
                    <td>{{ $post->title }}</td>
                    <td>
                        <a href="{{ route('post.show', $post->id) }}" class="btn btn-primary">Show Post</a>
                    </td>
                </tr>
                @endforeach
                </tbody>

            </table>
        </div>
    </div>
</div>
@endsection

Define the show route inside a web.php file. Add the following line of code inside a web.php file.

// web.php

Route::get('/post/show/{id}', 'PostController@show')->name('post.show');

Also, define the show() function inside the PostController.php file.

// PostController.php

public function show($id)
{
    $post = Post::find($id);

    return view('show', compact('post'));
}

Create a show.blade.php file inside the views folder and add the following code.

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-body">
                    <p>{{ $post->title }}</p>
                    <p>
                        {{ $post->body }}
                    </p>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Now you can see the individual posts. Fine till now.

The next step is to display the comments on this post.

Laravel Polymorphic morphMany relationship tutorial

Step 5: Create a form to add a comment.

First, create a CommentController.php file using the following command.

php artisan make:controller CommentController

We must create a form inside a show.blade.php file to add comments to a post.

Write the following code inside a show.blade.php file.

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-body">
                    <p><b>{{ $post->title }}</b></p>
                    <p>
                        {{ $post->body }}
                    </p>
                    <hr />
                    <h4>Add comment</h4>
                    <form method="post" action="{{ route('comment.add') }}">
                        @csrf
                        <div class="form-group">
                            <input type="text" name="comment_body" class="form-control" />
                            <input type="hidden" name="post_id" value="{{ $post->id }}" />
                        </div>
                        <div class="form-group">
                            <input type="submit" class="btn btn-warning" value="Add Comment" />
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

We have added a form that can add comments. Now, we need to define the route to store the comment.

// web.php

Route::post('/comment/store', 'CommentController@store')->name('comment.add');

Write the store() function and save the comment using the morphMany() relationship.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Comment;
use App\Post;

class CommentController extends Controller
{
    public function store(Request $request)
    {
        $comment = new Comment;
        $comment->body = $request->get('comment_body');
        $comment->user()->associate($request->user());
        $post = Post::find($request->get('post_id'));
        $post->comments()->save($comment);

        return back();
    }
}

If all is well, we can add the comments. Remember, we have not till now displayed the comments. So complete the save functionality, whose parent_id is null.

Laravel Nested Set Example

Step 6: Display the comment.

As we have set up the relationship between a Comment and a Post, we can easily pluck out all the comments related to a particular post.

Write the following code inside the show.blade.php file. I am writing the whole file to display the comments. Remember, these are the parent comments. We still need to create a reply button and show all the replies.

<!-- show.blade.php -->

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-body">
                    <p><b>{{ $post->title }}</b></p>
                    <p>
                        {{ $post->body }}
                    </p>
                    <hr />
                    <h4>Display Comments</h4>
                    @foreach($post->comments as $comment)
                        <div class="display-comment">
                            <strong>{{ $comment->user->name }}</strong>
                            <p>{{ $comment->body }}</p>
                        </div>
                    @endforeach
                    <hr />
                    <h4>Add comment</h4>
                    <form method="post" action="{{ route('comment.add') }}">
                        @csrf
                        <div class="form-group">
                            <input type="text" name="comment_body" class="form-control" />
                            <input type="hidden" name="post_id" value="{{ $post->id }}" />
                        </div>
                        <div class="form-group">
                            <input type="submit" class="btn btn-warning" value="Add Comment" />
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Add the comment, which will show us here in the same url.

Laravel Nesting Relationships

Step 7: Create a Reply form and save replies.

We must create a function called replies() inside the Comment.php model.

<?php

// Comment.php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function replies()
    {
        return $this->hasMany(Comment::class, 'parent_id');
    }
}

We must add a primary key as a parent_id in the replies function to fetch a reply based on a parent comment’s id.

Okay, now we must display all the comments and reply codes in the partial blade file.

This is because we need to nest the comment replies, and how much nesting is required depends upon the user interaction. So we can not predict the nesting levels.

To make it more and more flexible, we need to create partials and then repeat that partial to display the nested comment replies.

First, create a partials folder inside the resources >> views folder, and inside the partials folder, create one file called _comment_replies.blade.php.

Write the following code inside the _comment_replies.blade.php file.

<!-- _comment_replies.blade.php -->

 @foreach($comments as $comment)
    <div class="display-comment">
        <strong>{{ $comment->user->name }}</strong>
        <p>{{ $comment->body }}</p>
        <a href="" id="reply"></a>
        <form method="post" action="{{ route('reply.add') }}">
            @csrf
            <div class="form-group">
                <input type="text" name="comment_body" class="form-control" />
                <input type="hidden" name="post_id" value="{{ $post_id }}" />
                <input type="hidden" name="comment_id" value="{{ $comment->id }}" />
            </div>
            <div class="form-group">
                <input type="submit" class="btn btn-warning" value="Reply" />
            </div>
        </form>
        @include('partials._comment_replies', ['comments' => $comment->replies])
    </div>
@endforeach

Here, I have displayed all the replies with the text box. So it can do further nesting.

Now, this partial is expected to parameters.

  1. comments
  2. post_id.

So, when we include this partial inside the show.blade.php file, we must pass both parameters to access them here.

Also, we need to define the route to save the reply.

Add the following line of code inside routes >> web.php file.

// web.php

Route::post('/reply/store', 'CommentController@replyStore')->name('reply.add');

Our final web.php file looks like below.

<?php

// web.php

Route::get('/', function () {
    return view('welcome');
});

Auth::routes();

Route::get('/home', 'HomeController@index')->name('home');

Route::get('/post/create', 'PostController@create')->name('post.create');
Route::post('/post/store', 'PostController@store')->name('post.store');

Route::get('/posts', 'PostController@index')->name('posts');
Route::get('/post/show/{id}', 'PostController@show')->name('post.show');

Route::post('/comment/store', 'CommentController@store')->name('comment.add');
Route::post('/reply/store', 'CommentController@replyStore')->name('reply.add');

Also, define the replyStore() function inside the CommentController.php file.

I am writing here the complete code of the CommentController.php file.

<?php

// CommentController.php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Comment;
use App\Post;

class CommentController extends Controller
{
    public function store(Request $request)
    {
        $comment = new Comment;
        $comment->body = $request->get('comment_body');
        $comment->user()->associate($request->user());
        $post = Post::find($request->get('post_id'));
        $post->comments()->save($comment);

        return back();
    }

    public function replyStore(Request $request)
    {
        $reply = new Comment();
        $reply->body = $request->get('comment_body');
        $reply->user()->associate($request->user());
        $reply->parent_id = $request->get('comment_id');
        $post = Post::find($request->get('post_id'));

        $post->comments()->save($reply);

        return back();

    }
}

Almost both the function store and replyStore functions are the same here. We store parent comments and their replies in the same table. But, when we save a parent comment, the parent_id becomes null, and when we store any reply, then parent_id becomes its comment_id. So that is the difference.

Finally, our show.blade.php file looks like this.

<!-- show.blade.php -->

@extends('layouts.app')
<style>
    .display-comment .display-comment {
        margin-left: 40px
    }
</style>
@section('content')

<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-body">
                    <p><b>{{ $post->title }}</b></p>
                    <p>
                        {{ $post->body }}
                    </p>
                    <hr />
                    <h4>Display Comments</h4>
                    @include('partials._comment_replies', ['comments' => $post->comments, 'post_id' => $post->id])
                    <hr />
                    <h4>Add comment</h4>
                    <form method="post" action="{{ route('comment.add') }}">
                        @csrf
                        <div class="form-group">
                            <input type="text" name="comment_body" class="form-control" />
                            <input type="hidden" name="post_id" value="{{ $post->id }}" />
                        </div>
                        <div class="form-group">
                            <input type="submit" class="btn btn-warning" value="Add Comment" />
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Here, I have defined the CSS to display proper nesting.

Also, include the partials and pass both of the parameters.

  1. Post comments.
  2. Post id

We can add the parent comment from here but add the partials’ replies.

I have added the parent comment and its replies; our database table looks like this.

Laravel Nested Set Database

Also, our final output looks like below.

Laravel 5.6 Polymorphic Nested Relationship Example

That’s it.

Fork Me On Github

72 thoughts on “How to Create Comment Nesting in Laravel”

  1. I happened to have struggled with this in my previous project where I had to display user own comments to specific posts but I managed to get it to work until I got this today which I am sure will help with improving the version I had.

    Reply
  2. What i ɗo not realize is іf truth ƅe t᧐ld how you are not really a ⅼot more well-favored tһan you migһt be гight now.
    You are so intelligent. You realize ths considerably in relation to
    thiѕ topic, made me foг my part beⅼieve it frօm numerous numerous angles.
    Ӏts like mеn and women аre not involved սnless iit iѕ
    sоmething to accomplish witһ Girl gaga! Your personal stuffs nice.

    Аlways maintain itt up!

    Reply
  3. I ave got this error when I click in add comment button:

    Illuminate\Database\QueryException thrown with message “SQLSTATE[HY000]: General error: 1364 Field ‘parent_id’ doesn’t have a default value (SQL: insert into `comments` (`content`, `user_id`, `commentable_type`, `commentable_id`, `updated_at`, `created_at`) values (nice, 1, App\post, 3, 2018-08-14 22:00:03, 2018-08-14 22:00:03))”

    Reply
  4. magnificent points altogether, you simply won a new reader.
    What may you recommend about your post that you simply
    made a few days ago? Any certain?

    Reply
  5. Have you ever thought about publishing an ebook or
    guest authoring on other sites? I have a blog based upon on the same subjects you discuss and would really like to
    have you share some stories/information. I know my viewers would enjoy your
    work. If you’re even remotely interested, feel free to send me
    an e-mail.

    Reply
  6. Hey, thanks for awesome tutorial.
    Could you briefly explain the store function in the CommentController. It works fine, just wish to understand how the fields commentable_id and commentable_type get populated with their respective values.
    Thanks

    Reply
  7. Hi Krunal,

    Nice post! Maybe as an addition, I have added the following function in my model to recursively delete top-level comments.

    /**
    * Recursively delete the comment with replies all multiple levels
    * @throws Exception
    */
    public function deleteWithReplies()
    {
    if(count($this->replies) > 0) {
    // Delete children recursive
    foreach ($this->replies as $reply) {
    $reply->deleteWithReplies();
    }
    }
    $this->delete();
    }

    Best,

    Ruben

    Reply
  8. I was suggested this blog via my cousin. I am now not certain whether or not this
    submit is written through him as nbody else recognise such
    distinctive about my trouble. You’re amazing!
    Thanks!

    Reply
  9. it’s very usefull.
    but how to limit depth of replies comment? example: 3 depth of replies comment.
    because my web looks ugly, when reply messageis too much.

    Reply
  10. i have issue of infinity loop

    This page isn’t working 127.0.0.1 is currently unable to handle this request.
    HTTP ERROR 500

    how can i solve it thanks

    Reply
  11. Hi, thanks for this awesome tutorial.
    I follow this steps and i came across n+1 problem when comments nested more than 2 level.

    is there any suggestions ?

    Reply
  12. Nice tutorial thank u.
    Do you have one for uploading of mp3 files by user that can be downloaded by visitors. Would be of great help.

    Reply
  13. Good job. I really enjoyed its reading this post enough to search for writing this excellent article and helpful post thanks for sharing.

    Reply
  14. I want to show my affection for your generosity for men who should have help on that topic. Your personal dedication to passing the solution all through had been astonishingly advantageous and have consistently encouraged employees just like me to achieve their desired goals. Your warm and helpful help denotes so much a person like me and even more to my mates. Thank you; from everyone of us.

    Reply

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.