آموزش ایجاد سیستم نظرات پیشرفته تو در تو در لاراول

29 مرداد 1397
imgpsh_fullsize

در این مقاله قصد داریم ایجاد سیستم نظرات پیشرفته تو در تو در لاراول را به شما آموزش دهیم. در قسمت نظردهی برخی سایت ها، شما می توانید نظرات یک کاربر را reply کرده یا پاسخ بدهید و سپس کاربر دیگری نظرات کاربر دیگری را reply کرده و به این ترتیب.

به این ترتیب ایجاد نظرات تو در تو یکی از موارد خیلی مهم در طراحی برنامه های تحت وب است، که ما در مثال از پایه به آموزش این مطلب می پردازیم.

در این مثال از رابطه Polymorphic استفاده می کنیم.

سرفصل های این آموزش

1-نصب و پیکربندی لاراول

2-ایجاد یک مدل و migration

3-تعریف رابطه Polymorphic

4- تعریف View، کنترلر، و روت ها

5- ذخیره و نمایش پست ها

6- ایجاد یک فرم برای افزودن نظر

7- نمایش نظرات

8- ایجاد یک فرم reply (پاسخ) و ذخیره replyها

9- کدهای گیت هاپ

نصب و پیکربندی لاراول

laravel new comments

# or

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

سپس به پروژه بروید

cd comments

برنامه را در یک ویرایشگر باز کنید.

code .

پایگاه داده MySql را در فایل .env پیکربندی و سپس یک auth را با دستور زیر ایجاد کنید.

php artisan make:auth

سپس پایگاه داده را با دستور زیر migrate کنید.

php artisan migrate

ایجاد یک مدل  و Migration

یک مدل post به همراه migration آن را ایجاد کنید.

php artisan make:model Post -m

سپس Schema آن را در فایل Migration مربوط به post قرار دهید.

// create_posts_table

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

همچنین باید مدل Comment را به همراه migration آن تولید کنید:

php artisan make:model Comment -m

خب، حالا از رابطه Polymorphic برای ارتباط بین مدل ها استفاده می کنیم. پس باید Schema را با این روش ایجاد کنیم:

// 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 کنید.

php artisan migrat

ایجاد جدول در دیتابیس با لاراول

تعریف رابطه Polymorphic

در این مرحله باید رابطه Polymorphic را برای ارتباط بین مدل های برنامه تعریف کنید، پس دستورات زیر را در فایل app > Post.php ایجاد کنید.

<?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');
    }
}

در اینجا ما همه نظراتی را که مقدار parent_idشان برابر null است، می نویسیم. اینکار به این دلیل است که ما می خواهیم نظرات سطح والد را نمایش دهیم و همچنین این نظرات را ذخیره کنیم. به این ترتیب ما باید بین نظرات و پاسخ ها (reply) تفاوت قائل شویم.

همچنین هر پست متعلق به یک کاربر است. که می توانیم رابطه belongsTo را روی آن تعریف کنیم.

<?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');
    }
}

برای تعریف ارتباط بین نظر با پست ها کدهای زیر را در فایل Comment.php قرار دهید.

<?php

// Comment.php

namespace App;

use Illuminate\Database\Eloquent\Model;

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

تعریف view، کنترلر، و روت ها

یک کنترلر به نام PostController.php با استفاده از دستور زیر ایجاد کنید

php artisan make:controller PostController

در قدم بعدی باید برای ویوها (Views) مسیر (route) تعریف و پست ها را در پایگاه داده ذخیره کنیم. برای اینکار کدهای زیر را در فایل routes > web.php بنویسید.

<?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');

سپس کدهای زیر را در فایل PostController.php قرار دهید.

<?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
    }
}

حالا، یک فرم برای ایجاد پست درست می کنیم. برای اینکار یک فایل با نام post.blade.php در فولدر resource > views ایجاد کرده و کدهای زیر را در فایل post.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-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

حالا فایل resource > views > layouts > app.blade.php را باز و یک لینک برای ایجاد پست ایجاد کنید.

ما باید یک لینک به قسمت @else موجود در navigation bar اختصاص دهیم. چون می خواهیم اگر کاربری با موفقیت به برنامه لاگین کرد بتواند یک پست ایجاد کند و در غیر اینصورت نتواند.

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

سپس به آدرس http://localhost:8000/register  بروید و یک کاربر را ثبت کنید. بعد از اینکه با آن کاربر لاگین کردید می توانید لینک Create Post را در قسمت Navigation Bar  ببینید.

روی این لینک کلیک کنید تا به مسیر http://localhost:8000/post/create منتقل شوید. همان طور که می بینید در این صفحه یک فرم به همراه فیلدهای عنوان (title) و محتوای پست (body)، داریم.

ایجاد پست (مطلب) در لاراول

ذخیره و نمایش پست

در این مرحله برای ذخیره سازی پست ها را در پایگاه داده، کدهای زیر را متد store در فایل PostController.php قرار دهید.

<?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');

    }
}

بعد از اینکه پست ذخیره شد، ما به صفحه ای که لیست پستها را نمایش می دهد منتقل می شویم. کدهای زیر را در فایل web.php قرار دهید.

// web.php

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

همچنین باید متد index زیر را در فایل PostController.php ایجاد کنیم.

// PostController.php

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

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

فایل index.blade.php را درون فولدر views ایجاد و کدهای زیر را درون این فایل قرار دهید:

@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

مسیر (route) مربوط به نمایش پست ها را در فایل web.php قرار بنویسید:

// web.php

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

سپس متد show() را درون فایل PostController.php تعریف کنید

// PostController.php

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

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

فایل show.blade.php را درون فولدر views ایجاد و کدهای زیر را در آن قرار دهید:

@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

خب، به این ترتیب می توانید هر پست را به تفکیک ببینید. در مرحله بعد باید نظرات را در پست هایمان نمایش دهیم.

نمایش نظرات مربوط به هر پست در لاراول

ایجاد یک فرم و افزودن نظر

ابتدا با دستور زیر یک فایل به نام CommentController.php ایجاد کنید:

php artisan make:controller CommentController

سپس یک فرم درون فایل show.blade.php ایجاد کنید تا توسط آن بتوانیم برای هر پست کامنت بگذاریم.

کدهای زیر را در فایل 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>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

خب حالا یک فرم اضافه کردیم که می توانیم با آن نظر خود را ارسال کنیم. در نتیجه باید یک مسیر (route) برای ذخیره نظر تعریف کنیم.

// web.php

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

خب حالا یک متد Store() می نویسیم و نظرات خود را توسط رابطه morphMany() ذخیره می کنیم.

<?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();
    }
}

در صورتی که همه کارها را به درستی انجام داده باشید، اکنون کاربر می تواند نظرات خود را اضافه کند. دقت کنید که فعلا نمی توانیم نظرات را نمایش دهیم. با تکمیل ذخیره نظر مقدار parent_id برابر null است.

فرم سیستم نظرات پیشرفته تو در تو در لاراول

نمایش نظرات

خب ما رابطه بین نظر و پست ها را ایجاد کردیم، و اکنون می توانیم همه نظرات مربوط به یک پست را نمایش بدهیم. برای اینکار کدهای زیر را در فایل show.blade.php قرار دهید .دقت داشته باشید که همه این نظرات ، نظر والد (Parent) هستند. سپس یک دکمه reply (پاسخ) اضافه می کنیم و در قدم بعد همه نظرات پاسخ داده شده را نمایش می دهیم.

<!-- 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

حالا، نظر خود را اضافه کنید، می بینید که نظرات را در همان url نمایش می دهد.

ثبت نظرات در لاراول

ایجاد یک فرم پاسخ (Reply) و ذخیره پاسخ ها

یک متد با نام replies() درون مدل Comment.php ایجاد کنید.

<?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');
    }
}

در متد replies یک کلید اصلی به نام parent_id  ایجاد می کنیم. چون می خواهیم یک reply (پاسخ) مربوط به شناسه نظر والد آن را برگردانیم.

حالا کد مربوط به نمایش همه کامنت ها به همراه پاسخ های آنها را در یک فایل partial می نویسیم.

دلیل اینکار اینست که ما میخواهیم نظرات تو در تو ایجاد کنیم و تعداد سطح تو در تو بستگی به تعامل کاربرمان دارد و از طرفی از قبل نمی دانیم که تا چند سطح می خواهیم نظرات را تو در تو کنیم. برای اینکه انعطاف پذیری بیشتری به این قسمت بدهیم، می خواهیم این قسمت را در یک فایل partial قرار دهیم و برای نمایش هر نظر تو در تو از این فایل partial استفاده می کنیم.

پس یک فولدر به نام partial در مسیر reaource > views قرار دهید و داخل این فولدر یک فایل به نام comment_replies.blade.php ایجاد کنید.

سپس کدهای زیر را در این فایل قرار دهید:

<!-- _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

در اینجا من همه پاسخ ها را با یک textbox نمایش دادم.

حالا این partial نیاز به دریافت دو پارامتر دارد.

  • نظرات
  • post_id

موقعی که ما این partial را در فایل show.blade.php قرار می دهیم، باید این دو پارامتر ها را به آن پاس دهیم.

همچنین برای ذخیره پاسخ ها باید مسیر (route) مربوط به آن را ایجاد کنیم.

کدهای زیر را درون فایل routes > web.php قرار دهید.

// web.php

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

و در نهایت فایل web.php ما باید مطابق زیر باشد:

<?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');

سپس باید متد replyStore() را داخل فایل CmmentCotroller.php تعریف کنیم:

<?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();

    }
}

خب در اینجا متدهای store و replyStore تقریبا شبیه هم هستند. ما نظرات والد و reply (پاسخ) مربوط به هر کدام را درون یک جدول یکسان نگهداری میکنیم. اما موقعی که نظر والد را ذخیره میکنیم، مقدار فیلد parent_id برابر null است و وقتی که reply (پاسخ) را ذخیره کنیم، مقدار این فیلد برابر Comment_id است.این تفاوت آن است.

در انتها فایل show.blade.php مانند زیر است:

<!-- 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

در اینجا من باید استایل های css را برای نمایش درست نظرات تو در تو ایجاد کنم. سپس partial به همراه دو پارامتر زیر را به آن اضافه می کنم

  • نظرات پست
  • post id

ما می توانیم نظرات والد را از اینجا اضافه کنیم و نظرات reply را از partial اضافه کنیم. و در انتها، نظرات والد به همراه پاسخ های آنها را مانند زیر به پایگاه داده اضافه کردم:

جدول نظرات در mysql لاراول

همچنین خروجی نهایی مانند زیر است:

نظرات تو در تو در لاراول

خب آموزش این قسمت به پایان رسید. از این لینک می توانید کدهای مربوط به این آموزش را دانلود کنید.

نویسنده شوید
دیدگاه‌های شما (5 دیدگاه)

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

کامران
09 آذر 1399
خوبه بدک نیست

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

فریبا
30 مهر 1398
سلام ضمن تشکر از اموزش خوب و جامع شما در نمایش دکمه reply و عملیات مربوطه خطای زیر نمایش داده میشه: View [partials._comment_replies] not found. (View: C:\xampp\htdocs\comment\resources\views\show.blade.php) ممنون میشم بررسی و راهنمایی بفرمایید

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

مهشید
19 فروردین 1399
منم مشکل شما رو داشتم. برای حلش، فولدر partial رو نساختم. در نتیجه دو قسمتی که به صورت partials._comment_replies نوشته شده بود به صورت comment_replies نوشته شد. نتیجه درسته

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

فرید
15 اردیبهشت 1400
دقیقا

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

نازنین کریمی
29 اردیبهشت 1398
سلام ضمن تشکر وخسته نباشی از اموزش خیلی خوبتون...اگر برای نمایش کامنت و پاسخ ها بخایم از vuejs استفاده کنممی تونید راهنمایی کنید

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

فاطمه
03 بهمن 1397
ممنون

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

داریوش مسکین
30 مرداد 1397
سلام. بسیار عالی و جامع.

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

سعید
30 مرداد 1397
سلام. ممنونم از نظرت

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

nima
20 بهمن 1397
با سلام با تشکر از سایت و آموزش های خوبتون

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.