为什么从控制器调用 model 的时候 不能先调用 where 语句?

public function show(Category $category, Topic $topic)
    {
        $order = request('order');
        $topics = Topic::where('category_id', $category->id)
                        ->withOrder($order)
                        ->paginate(20);
        return view('topics.index', compact('topics', 'category'));
    }

为什么这里如果先调用where语句的话 查出来就是所有的分类数据

如果我改成Topic::withOrder($order)->where('category_id', $category->id) .... 就可以正常返回某个分类下的数据

《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
TimJuly
最佳答案

@王成涛
@OneStep
@许世桓

文章里写的是错的,所以运行出来就有问题了。

因为没有购买看不到文章里的内容,但是根据你们的回复看到示例代码是这样的。

出错的代码在这行:

$query = $this->recent();

运行这行代码会出现什么效果呢?就是你会得到一个新的Illuminate\Database\Eloquent\Builder对象,所以之前的where条件会丢失。所以正确的写法应该是:

public function scopeWithOrder($query, $order)
{
    switch ($order) {
        case 'recent':
            $query->recent();
            break;

        default:
            $query->recentReplied();
            break;
    }

    return $query->with('user', 'category');
}

@Summer
建议修改文章,并收录到 Laravel 最佳错误实践。

6年前 评论
讨论数量: 13
TimJuly

@王成涛
@OneStep
@许世桓

文章里写的是错的,所以运行出来就有问题了。

因为没有购买看不到文章里的内容,但是根据你们的回复看到示例代码是这样的。

出错的代码在这行:

$query = $this->recent();

运行这行代码会出现什么效果呢?就是你会得到一个新的Illuminate\Database\Eloquent\Builder对象,所以之前的where条件会丢失。所以正确的写法应该是:

public function scopeWithOrder($query, $order)
{
    switch ($order) {
        case 'recent':
            $query->recent();
            break;

        default:
            $query->recentReplied();
            break;
    }

    return $query->with('user', 'category');
}

@Summer
建议修改文章,并收录到 Laravel 最佳错误实践。

6年前 评论

withOrder这个方法是关联模型吧? 如果withOrder 放前面where条件是跟withOrder 关联的结果里面筛选;
where 放前面 是先筛选 然后列出关联数据;
都能返回数据, 不过返回数据的结果差异体现在这里, 不过我这样理解对不

6年前 评论

@OneStep
我现在跟着教程来做
我之前的代码是

$topics = Topic::where('category_id', $category->id)->paginate(20);

根据这个category->id 查到某个分类下的所有话题。这个时候结果是对的

但是学到后面一节的时候需要排序,在Topic模型里增加了

// 通过order进行话题排序
    public function scopeWithOrder($query, $order)
    {
        switch ($order) {
            case 'recent':
                $query = $this->recent();
                break;

            default:
                $query = $this->recentReplied();
                break;
        }
        return $query->with('user', 'category');
    }

    // 按照创建时间倒序排列
    public function scopeRecent($query)
    {
        return $query->orderBy('created_at', 'desc');   
    }

    public function scopeRecentReplied($query)
    {
        return $query->orderBy('updated_at', 'desc');
    }

然后我在topic控制器中调用,如果where放在前面竟然查询到所有分类的数据
但是如果我先调用withOrder在调用where就是正常的数据。

6年前 评论

理解的简单的是:
先把category和topic的表现连表查询,然后连表查询后,在进行where(),不然你先where在连表查询,这样是数据是不对的嘛。

6年前 评论

@许世桓 可能是这样。 我感觉也是底层应该要先取到符合条件的数据以后在进行排序。 还没深入看orm那里,对内部的处理方式还不理解。 谢谢解答了

6年前 评论
66

你可以打印出SQL看看那 ->toSql() 没有什么比自己看下SQL跟能解决问题的了

6年前 评论

file

file
两种写法结果确实不一致

6年前 评论

@王成涛 是的。我和你这sql一样。主要就是最后一行sql里的in条件的问题。

6年前 评论
TimJuly

@王成涛
@OneStep
@许世桓

文章里写的是错的,所以运行出来就有问题了。

因为没有购买看不到文章里的内容,但是根据你们的回复看到示例代码是这样的。

出错的代码在这行:

$query = $this->recent();

运行这行代码会出现什么效果呢?就是你会得到一个新的Illuminate\Database\Eloquent\Builder对象,所以之前的where条件会丢失。所以正确的写法应该是:

public function scopeWithOrder($query, $order)
{
    switch ($order) {
        case 'recent':
            $query->recent();
            break;

        default:
            $query->recentReplied();
            break;
    }

    return $query->with('user', 'category');
}

@Summer
建议修改文章,并收录到 Laravel 最佳错误实践。

6年前 评论

@TimJuly 确实如您所说的这样。修改后无论先调用谁都可以了。感谢 :clap:

6年前 评论

@TimJuly 感谢解答,恍然大悟 :+1: :+1: :+1:

6年前 评论

@TimJuly 请教一下,这里的这个

public function scopeWithOrder($query, $order)
    {
        // 不同的排序,使用不同的数据读取逻辑
        switch ($order) {
            case 'recent':
                $query->recent();
                break;

            default:
                $query->recentReplied();
                break;
        }
        // 预加载防止 N+1 问题
        return $query->with('user', 'category');
    }

这个$query应该怎么理解呢,是查询构造器吗?我看后面的调用的时候并没有传入这个参数啊?

class TopicsController extends Controller
{
.
.
.

    public function index(Request $request, Topic $topic)
    {
        $topics = $topic->withOrder($request->order)->paginate(20);
        return view('topics.index', compact('topics'));
    }

这里只传入了$order这个参数呢。

5年前 评论

@TimJuly 总结,$this是指向新对象。所以会新建一个查询构造器,并且把之前链式调用的冲掉了。

5年前 评论

原来模型内部的方法调用竟然还有这么大的一个坑!$query->recent()$this->recent()的区别,学习了。

也可以简化成一个函数

    public function scopeWithOrder($query, $order='updated_at'){
        return $query->orderByDesc($order)->with('user', 'category');
    }

前端链接的查询参数直接传递字段名

    http://x.x.x/xx?order=updated_at
    http://x.x.x/xx?order=created_at

由于避免了recent等字符串的映射过程,Topic模型的代码简单多了(也避开了陷阱)。以下是控制器方法

    public function show(Category $category, Request $request)
    {
        $order= $request->order ?: 'updated_at';
        $topics= Topic::where('category_id', $category->id)->withOrder($order)->paginate();
        return view('topics.index', compact('topics', 'category'));
    }
4年前 评论

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