安装了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'=>int4289protected'lastPage'=>int143protected'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使用的一种代码设计模式,

/***Handledynamicstaticmethodcallsintothemethod.**@paramstring$method*@paramarray$parameters*@returnmixed*/publicstaticfunction__callStatic($method,$parameters){$instance=newstatic;returncall_user_func_array([$instance,$method],$parameters);}

__callStatic是php类的魔术方法

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

publicstaticmixed__callStatic(string$name,array$arguments)

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


laravel Model的__callStatic实现的业务

newstatic;

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这魔术方法。

/***Handledynamicmethodcallsintothemodel.**@paramstring$method*@paramarray$parameters*@returnmixed*/publicfunction__call($method,$parameters){if(in_array($method,['increment','decrement'])){returncall_user_func_array([$this,$method],$parameters);}$query=$this->newQuery();returncall_user_func_array([$query,$method],$parameters);}

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

User::where();

$user=newUser;$user->paginate(15);

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

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


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

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


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

'Illuminate\Database\Eloquent\Builder'/***Getanewquerybuilderforthemodel'stable.**@return\Illuminate\Database\Eloquent\Builder*/publicfunctionnewQuery()


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

其中paginate方法

/***Paginatethegivenquery.**@paramint$perPage*@paramarray$columns*@paramstring$pageName*@paramint|null$page*@return\Illuminate\Contracts\Pagination\LengthAwarePaginator**@throws\InvalidArgumentException*/publicfunctionpaginate($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):newCollection;returnnewLengthAwarePaginator($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分页源码分析,很多地方有待深入,不过看一次源码,提高很大。