安装了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分页源码分析,很多地方有待深入,不过看一次源码,提高很大。