You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel评论编辑/删除误改帖子问题及嵌套回复实现求助

问题描述

正在开发一个帖子与评论系统,已创建Comment和Post控制器及模型。当登录用户(含管理员)编辑或删除评论时,实际被修改的却是对应的帖子,而非评论。相关代码如下:

Comment 控制器代码

public function edit(Comment $comment)
{
    if (auth()->user()->is_admin !== 1) {
        if ($comment->user_id !== Auth::id()) {
            abort(403);
        }
    }

    return view('comments.edit', ['comment' => $comment]);
}

public function destroy(Comment $comment)
{

    if (auth()->user()->is_admin !== 1) {
        if ($comment->user_id !== Auth::id()) {
            abort(403);
        }
    }
    //        dd($comment);
    $comment->delete();
    return redirect('posts/' . $comment->post->slug)->with('success', 'Comment deleted successfuly');
}

public function store(Request $request, Comment $comment, $slug)
{
    $request->validate([
        'body' => 'required'
    ]);
    $post_id = Post::where('slug', $slug)->pluck('id');
    //        dd($post_id[0]);

    $comment->create([
        'user_id' => $request->user()->id,
        'post_id' => $post_id[0],
        'body' => $request->input('body'),
    ]);

    return redirect()->back();
}

// update
public function update( Comment $comment,Post $post)
{

    $comment->update([
        'body' => request()->body
    ]);

    return redirect('posts/' . $comment->post->slug)->with('success', 'Comment updated successfuly');
}

评论表单代码

<x-panel >
<form action="{{route('comments.store', $post)}}" method="POST">
    @csrf
    <div class="mt-6">
        <textarea name="body" cols="30" rows="" class="rounded w-full text-sm  overflow-y-scroll" placeholder="Comment as {{auth()->user()->name}}" required></textarea>
    </div>
    <div class="flex justify-end  border-gray-200 pt-3">
        <button type="submit" class="bg-gray-600 text-white uppercase font-semibold text-md px-10 rounded-2xl hover:bg-gray-800 hover:text-white">Post</button>
    </div>
  </form>
 </x-panel>

路由代码

Route::get('/posts',[PostController::class,'index'])->name('posts.index');
Route::get('/myposts',[PostController::class,'indexx'])->name('profileposts');
Route::get('/posts/{post:slug}', [PostController::class, 'show'])->name('posts.show');
Route::get('/posts/create/post', [PostController::class, 'create'])->name('posts.create'); //shows create post form
Route::post('/posts/create/post', [PostController::class, 'store'])->name('posts.store'); //saves the created post to the database
Route::get('/posts/{post:slug}/edit', [PostController::class, 'edit'])->name('posts.edit'); //shows edit post form
Route::put('/posts/{post:slug}/edit', [PostController::class, 'update'])->name('posts.update'); //commits edited post to the database
Route::delete('/posts/{post}/', [PostController::class, 'destroy'])->name('posts.destroy'); //deletes post from the database

Route::post('/posts/{post:slug}/comments', [CommentController::class, 'store'])->name('comments.store'); //store comment
Route::get('posts/comments/{comment:slug}/edit', [CommentController::class, 'edit'])->name('comments.edit'); //edit comment
Route::put('posts/comments/{comment:slug}/edit', [CommentController::class, 'update'])->name('comments.update'); //commits edited comment to the database
Route::delete('posts/comments/{comment:slug}', [CommentController::class, 'destroy'])->name('comments.destroy');

现寻求解答两个问题:

  1. 导致评论编辑/删除时误修改帖子的原因是什么?
  2. 如何实现评论的嵌套回复功能?

问题解答

1. 评论编辑/删除误操作帖子的原因

核心原因:路由匹配顺序与规则冲突

Laravel路由按从上到下顺序匹配,你的Post控制器删除/编辑路由定义在Comment路由之前,且Post路由的通配符会匹配到Comment的请求路径:

  • Post删除路由Route::delete('/posts/{post}/', ...)中,{post}通配符会把/posts/comments/xxx里的comments/xxx识别为Post标识,触发PostController的destroy方法删除帖子。
  • Post编辑路由Route::put('/posts/{post:slug}/edit', ...)同理,会把/posts/comments/xxx/edit里的comments/xxx识别为Post的slug,触发PostController的update方法修改帖子。

次要问题:

  • Comment模型可能无slug字段:路由用{comment:slug}做隐式绑定,但如果Comment表没有slug字段,Laravel无法正确解析Comment实例,加剧匹配异常。
  • CommentController的update方法冗余参数:声明了Post $post参数但路由未传递相关参数,会导致Laravel额外解析Post实例,引发不必要的匹配问题。

2. 实现评论嵌套回复功能的步骤

步骤1:修改Comment表结构

添加parent_id字段标记父评论:

// 创建迁移文件
php artisan make:migration add_parent_id_to_comments_table --table=comments

// 迁移文件中添加字段
public function up()
{
    Schema::table('comments', function (Blueprint $table) {
        $table->unsignedBigInteger('parent_id')->nullable();
        $table->foreign('parent_id')->references('id')->on('comments')->onDelete('cascade');
    });
}

// 执行迁移
php artisan migrate

步骤2:在Comment模型定义关联关系

class Comment extends Model
{
    // 其他代码...

    // 关联父评论
    public function parent()
    {
        return $this->belongsTo(Comment::class, 'parent_id');
    }

    // 关联子评论(递归加载子评论)
    public function children()
    {
        return $this->hasMany(Comment::class, 'parent_id')->with('children');
    }

    // 关联所属用户(优化加载)
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

步骤3:修改CommentController的store方法,支持接收parent_id

public function store(Request $request, $slug)
{
    $request->validate([
        'body' => 'required',
        'parent_id' => 'nullable|exists:comments,id'
    ]);

    $post = Post::where('slug', $slug)->firstOrFail();

    Comment::create([
        'user_id' => $request->user()->id,
        'post_id' => $post->id,
        'parent_id' => $request->input('parent_id'),
        'body' => $request->input('body'),
    ]);

    return redirect()->back();
}

注:移除冗余的Comment $comment参数,直接用模型静态方法创建更简洁。

步骤4:修改视图,支持回复表单与递归渲染嵌套评论

4.1 帖子详情页的评论列表(添加回复按钮与表单)
{{-- 只渲染顶级评论 --}}
@foreach($post->comments->whereNull('parent_id') as $comment)
    <div class="comment">
        <p>{{ $comment->user->name }}: {{ $comment->body }}</p>
        <button onclick="toggleReplyForm({{ $comment->id }})">回复</button>
        {{-- 回复表单 --}}
        <div id="reply-form-{{ $comment->id }}" class="hidden mt-2">
            <form action="{{ route('comments.store', $post->slug) }}" method="POST">
                @csrf
                <input type="hidden" name="parent_id" value="{{ $comment->id }}">
                <textarea name="body" required placeholder="写下你的回复..."></textarea>
                <button type="submit">提交回复</button>
            </form>
        </div>
        {{-- 递归渲染子评论 --}}
        @include('comments.children', ['comments' => $comment->children])
    </div>
@endforeach
4.2 创建子评论局部视图(resources/views/comments/children.blade.php)
@foreach($comments as $comment)
    <div class="ml-6 mt-2 comment">
        <p>{{ $comment->user->name }}: {{ $comment->body }}</p>
        <button onclick="toggleReplyForm({{ $comment->id }})">回复</button>
        <div id="reply-form-{{ $comment->id }}" class="hidden mt-2">
            <form action="{{ route('comments.store', $comment->post->slug) }}" method="POST">
                @csrf
                <input type="hidden" name="parent_id" value="{{ $comment->id }}">
                <textarea name="body" required placeholder="写下你的回复..."></textarea>
                <button type="submit">提交回复</button>
            </form>
        </div>
        {{-- 递归渲染更深层级的子评论 --}}
        @include('comments.children', ['comments' => $comment->children])
    </div>
@endforeach
4.3 添加JS切换回复表单显示状态
<script>
function toggleReplyForm(commentId) {
    const form = document.getElementById(`reply-form-${commentId}`);
    form.classList.toggle('hidden');
}
</script>

步骤5:优化路由(可选)

将Comment路由放在Post路由之前,避免后续匹配问题,同时将{comment:slug}改为{comment}(用默认id绑定即可,无需slug):

// 先定义Comment路由
Route::post('/posts/{post:slug}/comments', [CommentController::class, 'store'])->name('comments.store');
Route::get('posts/comments/{comment}/edit', [CommentController::class, 'edit'])->name('comments.edit');
Route::put('posts/comments/{comment}/edit', [CommentController::class, 'update'])->name('comments.update');
Route::delete('posts/comments/{comment}', [CommentController::class, 'destroy'])->name('comments.destroy');

// 再定义Post路由
Route::get('/posts',[PostController::class,'index'])->name('posts.index');
// ... 其他Post路由

内容的提问来源于stack exchange,提问作者Nelson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 04:22:53