scopeWithOrder ($query, $order), $query 是怎么来的?

像这种问题,一般是先查文档,但文档中也只是写了如何去用,而没有提到$query具体是怎么来的。
因此,可以去看源码,源码分析步骤:
找到 withOrder 方法,由于步骤太多,前面的各种magic method跳转全部省略。。。(此处省略1万字)。

最终在 vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php 中可以找到 __call()方法中有如下代码段:

    if (method_exists($this->model, $scope = 'scope'.ucfirst($method))) {
        return $this->callScope([$this->model, $scope], $parameters);
    }

不难看出是检查当前正在运行的model即topic model中是否有scope开头的方法,我们已经提前在其中写了scopeWithOrderd的方法,因此,会调用Eloquent/Builder中的callScope方法。

    /**
        * Apply the given scope on the current builder instance.
        *
        * @param  callable  $scope
        * @param  array  $parameters
        * @return mixed
        */
    protected function callScope(callable $scope, $parameters = [])
    {
        array_unshift($parameters, $this);  //将$this即当前的builder instance放在 $parameters 数组的最开始

        $query = $this->getQuery();

        // We will keep track of how many wheres are on the query before running the
        // scope so that we can properly group the added scope constraints in the
        // query as their own isolated nested where statement and avoid issues.
        $originalWhereCount = is_null($query->wheres)
                    ? 0 : count($query->wheres);

        $result = $scope(...array_values($parameters)) ?? $this;  // 这里是进行回调,回调的是我们在上一步中调用callScope方法时的
        那个第一参数所代表的方法,也就是topic model中的scopeWithOrderd方法。 而array_values($parameters)是依次取出数组中的值返回一个数字数组。
        而...$arr这种写法时将$arr数组中的元素全部拆开依次放入对应的函数的参数列表。由于数组中第一个元素是当前的builder instance,
        这样就将当前的builder instance传到了topic model中的scopeWithOrderd方法的$query参数变量中,这样对$query的操作,就是对当前的builder instance的操作了。。
        因此后面的操作无非可以看成是SQL语句的一些order,where的操作。

        if (count((array) $query->wheres) > $originalWhereCount) {
            $this->addNewWheresWithinGroup($query, $originalWhereCount);
        }

        return $result;   //最后这里返回的还是一个eloquent builder instance.
    }

通过上面的代码注释,我们就可以基本了解scopeWithOrder ($query, $order)中的 $query 是怎么来的了。
同样,理解了上述代码,我们也不难看出在控制器中,各种条件的限制操作都是可以互换位置的,比如下面的代码:

    $topics = $topic->withOrder($request->order)->where('category_id', $category->id)->paginate(20);

完全可以换为

    $topics = $topic->where('category_id', $category->id)->withOrder($request->order)->paginate(20);

对运行效率没有影响。经过数十次测试,也验证了这一点。

日拱一卒
本帖已被设为精华帖!
本帖由系统于 3年前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 2

这里是进行回调,回调的是我们在上一步中调用callScope方法时的
那个第一参数所代表的方法,也就是topic model中的scopeWithOrderd方法。大神scopeWithOrderd这个具体实现能在说清楚点吗

5年前 评论

讲的通俗易懂, 看懂了 :+1:

4年前 评论

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