Lavavel笔记 Eloquent ORM分页源码分析
admin
2023-06-19 16:41:42
0次

安装了laravel-debugbar后打开一个列表页面,发现页面输出有两个 select count(*) 语句,这是一个严重的设计缺陷呀。


查看代码

$users = User::where('votes', '>', 100)->paginate(15);
$count = User::where('votes', '>', 100)->count();

之前就感觉paginate分页应该是使用了count,但是不知道怎么取总量数据所以又写了一个count()。

var_dump($users);
object(Illuminate\Pagination\LengthAwarePaginator)[306]  
  protected 'total' => int 4289
  protected 'lastPage' => int 143
  protected 'items' => 
    object(Illuminate\Database\Eloquent\Collection)[307]      
    protected 'items' => 
        array (size=30)
          0 => ....

返回数据里面 的确有 protected 'total',但是protected不能访问呀!

仔细看了一下文档,$results->total(),原来取total需要的是方法,而不是属性。

弄明白total的获取以后,对paginate这个分页方法产生了兴趣。于是看了一下源码。

paginate这个方法最后使用了

Illuminate\Pagination\LengthAwarePaginator

这个类是怎么调用的呢?

无论是User::where('votes', '>', 100)->paginate(15)还是User::paginate(15),

User继承着Illuminate\Database\Eloquent\Model这个ORM类,但是在Model并没有where和paginate这些方法或静态方法,这是laravl使用的一种代码设计模式,

    /**
     * Handle dynamic static method calls into the method.
     *
     * @param  string  $method
     * @param  array  $parameters
     * @return mixed
     */
    public static function __callStatic($method, $parameters)
    {
        $instance = new static;
        return call_user_func_array([$instance, $method], $parameters);
    }

__callStatic是php类的魔术方法

http://php.net/manual/zh/language.oop5.overloading.php#object.callstatic

 public static mixed __callStatic ( string $name , array $arguments )

在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。
$name 参数是要调用的方法名称。$arguments 参数是一个枚举数组,包含着要传递给方法 $name 的参数。


laravel Model的__callStatic实现的业务

new static;

new当前model,也就是new User,这个是static静态延迟绑定的使用,可以和“new self;”使用进行比较。


call_user_func_array([$instance, $method], $parameters);

主要是这段,call_user_func_array 调用了new static类即User的where或paginate方法,传递 $parameters参数。

然后,然后,然后,User这个继承Model类里面仍然没有where或paginate方法,那么使用 Model的__call这魔术方法。

    /**
     * Handle dynamic method calls into the model.
     *
     * @param  string  $method
     * @param  array  $parameters
     * @return mixed
     */
    public function __call($method, $parameters)
    {
        if (in_array($method, ['increment', 'decrement'])) {
            return call_user_func_array([$this, $method], $parameters);
        }
        $query = $this->newQuery();
        return call_user_func_array([$query, $method], $parameters);
    }

开始还以为这么处理多了一层魔术方法,浪费效率,想想突然明白了,

User::where();
$user = new User; 
$user->paginate(15);

代码是一样的,只不过用__callStatic的 new static代替了“new User;”,

用call_user_func_array代替了调用函数和传参,lavarel的简洁可见一斑。


继续分析,如果调用的方法是 increment或者decrement,那么使用的是User类的方法。

否则使用 $this->newQuery();


newQuery这个方法的层级太深,没能理解,不过看注释,主要是调用

'Illuminate\Database\Eloquent\Builder'

    /**
     * Get a new query builder for the model's table.
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function newQuery()


Builder这个类里面的where和paginate方法,就是ORM使用的方法;

其中paginate方法

    /**
     * Paginate the given query.
     *
     * @param  int  $perPage
     * @param  array  $columns
     * @param  string  $pageName
     * @param  int|null  $page
     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
     *
     * @throws \InvalidArgumentException
     */
    public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
    {
        $page = $page ?: Paginator::resolveCurrentPage($pageName);
        $perPage = $perPage ?: $this->model->getPerPage();
        $query = $this->toBase();
        $total = $query->getCountForPagination();
        $results = $total ? $this->forPage($page, $perPage)->get($columns) : new Collection;
        return new LengthAwarePaginator($results, $total, $perPage, $page, [
            'path' => Paginator::resolveCurrentPath(),
            'pageName' => $pageName,
        ]);
    }

paginate调用最后使用了LengthAwarePaginator类,

所以最后var_dump($user) 是“object(Illuminate\Pagination\LengthAwarePaginator)[306]”

LengthAwarePaginator只是分页类,与数据层无关,即Eloquent ORM和分页是分离的。


以上就是Lavavel的Eloquent ORM分页源码分析,很多地方有待深入,不过看一次源码,提高很大。


相关内容

热门资讯

空调连续工作多久需要停机休息 空调连续工作的时间取决于多个因素,包括空调的型号、功率、使用环境和维护情况等。一般来说,空调可以连续...
空调哪个符号是除湿的 空调除湿功能通常用一个水滴形状的符号表示。不同品牌和型号的空调遥控器可能会有所差异,但一般来说,除湿...
格力空调多长时间加一次氟利昂 1、一般情况下,空调对氟利昂的使用量是非常小的,所以在空调使用了四年左右的时候再考虑加也没问题。2、...
空调用多长时间需要加氟一次 一般情况下是五年。当出现以下这几种情况时,空调才需要加氟。1、空调的室内机和室外机各有两个接口,当接...
格调生活·C标思享荟:卡萨帝在... 9月12日的郑州龙湖金融岛,夜风轻拂,灯光璀璨。“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑...
品牌价值破千亿、高端份额持续领... 9月12日,“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑州站”在郑州龙湖金融岛举办。2026...
空调主板坏了修800还是换新的 空调主板坏了是修还是换新,需要考虑多个因素,以下是一些建议:1. 维修成本:如果维修费用较低,比如 ...
空调开多久需要休息多长时间 问题:空调开多久需要休息多长时间回答:1、在使用空调的过程中,一般空调开7~8个小时左右需要休息2~...
中央空调多长时间清洗一次 1、一般空调开机前清洗一次即可,空调开机中间时段再清洗一次,空调关机时清洗一次,这样比较合理。2、空...
中央空调氟利昂多久加一次 看空调制热和制冷程度,很多人的观点认为氟利昂必须一年放一次,其实这个观点是错误的,理解上面有点偏差,...