使用 can 标签判断是否有删除回复的权限会导致 N+1 问题,有什么更好的办法吗?

@can('destroy', $reply)
    <span class="meta pull-right">
    <form action="{{ route('replies.destroy', $reply->id) }}" method="post">
        {{ csrf_field() }}
        {{ method_field('DELETE') }}
        <button type="submit" class="btn btn-default btn-xs pull-left">
            <i class="glyphicon glyphicon-trash"></i>
        </button>
    </form>
</span>
@endcan

这样写会导致每一条回复都去查找一次数据库
file
有什么更好的办法吗

《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
最佳答案

传递$topic 就不用关联查询了,但是删除按钮的表单是 注入模型 ID 给路由的,要改路由很麻烦,所以新增一个授权方法,跟destroy分开。

# show.blade.php
@include('topics._reply_list', ['replies' => $topic->replies()->with('user')->get(), 'topic' => $topic])

# _reply_list.blade.php 把@can改为 
@can('showdel', [$reply, $topic])
....
@endcan

最后是 ReplyPolicy.php

use App\Models\Topic;
class ReplyPolicy extends Policy
{
.....
    public function showdel(User $user, Reply $reply, Topic $topic)
    {
        return $user->isAuthorOf($reply) || $user->isAuthorOf($topic);
    }
}
6年前 评论
讨论数量: 4

不需要@smithadam 这么麻烦的方法,只需要在传入$replies前加上topic预加载即可,具体如下:
文件resources/views/topics/show.blade.php大概第70行左右的:

@include('topics._reply_list', ['replies' => $topic->replies()->with('user')->get()])

改成:

@include('topics._reply_list', ['replies' => $topic->replies()->with('user', 'topic')->get()])

如图:
file

5年前 评论
Athos 4年前

传递$topic 就不用关联查询了,但是删除按钮的表单是 注入模型 ID 给路由的,要改路由很麻烦,所以新增一个授权方法,跟destroy分开。

# show.blade.php
@include('topics._reply_list', ['replies' => $topic->replies()->with('user')->get(), 'topic' => $topic])

# _reply_list.blade.php 把@can改为 
@can('showdel', [$reply, $topic])
....
@endcan

最后是 ReplyPolicy.php

use App\Models\Topic;
class ReplyPolicy extends Policy
{
.....
    public function showdel(User $user, Reply $reply, Topic $topic)
    {
        return $user->isAuthorOf($reply) || $user->isAuthorOf($topic);
    }
}
6年前 评论

还可以在 xinhuo 所写的基础上加上两个方法,实现按回复时间倒序排列以及分页。

@include('topics._reply_list', ['replies' => $topic->replies()->with('user', 'topic')->recent()->paginate(10)])

然后在 resources/views/topics/_reply_list.blade.php 的末尾加上

    ...
    {!! $replies->appends(Request::except('page'))->render() !!}
</div>
5年前 评论

传递$topic 就不用关联查询了,但是删除按钮的表单是 注入模型 ID 给路由的,要改路由很麻烦,所以新增一个授权方法,跟destroy分开。

# show.blade.php
@include('topics._reply_list', ['replies' => $topic->replies()->with('user')->get(), 'topic' => $topic])

# _reply_list.blade.php 把@can改为 
@can('showdel', [$reply, $topic])
....
@endcan

最后是 ReplyPolicy.php

use App\Models\Topic;
class ReplyPolicy extends Policy
{
.....
    public function showdel(User $user, Reply $reply, Topic $topic)
    {
        return $user->isAuthorOf($reply) || $user->isAuthorOf($topic);
    }
}
6年前 评论

不需要@smithadam 这么麻烦的方法,只需要在传入$replies前加上topic预加载即可,具体如下:
文件resources/views/topics/show.blade.php大概第70行左右的:

@include('topics._reply_list', ['replies' => $topic->replies()->with('user')->get()])

改成:

@include('topics._reply_list', ['replies' => $topic->replies()->with('user', 'topic')->get()])

如图:
file

5年前 评论
Athos 4年前
ruodee

解决N+1的问题,首先考虑预加载。:+1:

5年前 评论

还可以在 xinhuo 所写的基础上加上两个方法,实现按回复时间倒序排列以及分页。

@include('topics._reply_list', ['replies' => $topic->replies()->with('user', 'topic')->recent()->paginate(10)])

然后在 resources/views/topics/_reply_list.blade.php 的末尾加上

    ...
    {!! $replies->appends(Request::except('page'))->render() !!}
</div>
5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!