Laravel Eloquent 调用逻辑解析

前言

使用过Laravel的人应该都熟悉下面这一条语句,主要用来根据主键ID获取一条数据库记录。

Photo::find(2)

下面就从这条语句开始,逐条解析源代码的执行流程。

解析过程

本文以Laravel 5.1 LTS 为例子
所在文件:app/Models/Photo.php

namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Photo extends Model
{
//some code
}

可以看到继承了:Illuminate\Database\Eloquent\Model,打开文件可以发现并没有find方法,很简单方法不存在会触发下面的代码调用。

所在文件:vendor/laravel/framework/src/Illuminate\Database\Eloquent\Model.php

/**
* 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);
}

我们可以看看实际的调用情况
img
$instance 是Photo的实例,这里头有个小细节就是new static 和 new self的区别,具体可以看这里 New self vs. new static

接着会调用如下方法

所在文件:vendor/laravel/framework/src/Illuminate\Database\Eloquent\Model.php

/**
* 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);
}

因为不是increment和decrement方法,所以跳出去用newQuery实例出一个Eloquent Builder。

所在文件:vendor/laravel/framework/src/Illuminate\Database\Eloquent\Model.php

/**
* Get a new query builder for the model's table.
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function newQuery()
{
$builder = $this->newQueryWithoutScopes();
return $this->applyGlobalScopes($builder);
}

跟踪newQueryWithoutScopes方法

所在文件:vendor/laravel/framework/src/Illuminate\Database\Eloquent\Model.php

/**
* Get a new query builder that doesn't have any global scopes.
*
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function newQueryWithoutScopes()
{
$builder = $this->newEloquentBuilder(
$this->newBaseQueryBuilder()
);
// Once we have the query builders, we will set the model instances so the
// builder can easily access any information it may need from the model
// while it is constructing and executing various queries against it.
return $builder->setModel($this)->with($this->with);
}

注意代码,$this->newBaseQueryBuilder() 是实例化一个\Illuminate\Database\Query\Builder实例,而newQueryWithoutScopes是实例化\Illuminate\Database\Eloquent\Builder出来,这两个Builder需要区分开,两者的关系可以由下面代码看出

所在文件:vendor/laravel/framework/src/Illuminate\Database\Eloquent\Model.php

/**
* Create a new Eloquent query builder for the model.
*
* @param \Illuminate\Database\Query\Builder $query
* @return \Illuminate\Database\Eloquent\Builder|static
*/
public function newEloquentBuilder($query)
{
return new Builder($query);
}

两者的关系,我们可以通过Debug更清晰准确的观察出来。
img

而 $builder->setModel($this)->with($this->with) 这句主要是Photo的内的相关变量传给Builder以便做一些判断。

到此为止,就已经把最重要的\Illuminate\Database\Eloquent\Builder实例化好了,接着_call方法就把执行find的方法转移给了\Illuminate\Database\Eloquent\Builder

所在文件:vendor/laravel/framework/src/Illuminate\Database\Eloquent\Builder.php

/**
* Find a model by its primary key.
*
* @param mixed $id
* @param array $columns
* @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|null
*/
public function find($id, $columns = ['*'])
{
if (is_array($id)) {
return $this->findMany($id, $columns);
}
$this->query->where($this->model->getQualifiedKeyName(), '=', $id);
return $this->first($columns);
}

\Illuminate\Database\Eloquent\Builder又把最终执行where的权力交给了\Illuminate\Database\Query\Builder

所在文件:vendor/laravel/framework/src/Illuminate\Database\Query\Builder.php

/**
* Add a basic where clause to the query.
*
* @param string|array|\Closure $column
* @param string $operator
* @param mixed $value
* @param string $boolean
* @return $this
*
* @throws \InvalidArgumentException
*/
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
// If the column is an array, we will assume it is an array of key-value pairs
// and can add them each as a where clause. We will maintain the boolean we
// received when the method was called and pass it into the nested where.
if (is_array($column)) {
return $this->whereNested(function ($query) use ($column) {
foreach ($column as $key => $value) {
$query->where($key, '=', $value);
}
}, $boolean);
}
// 下面还有代码,太长就不贴了...
}

到此结束,完整的调用栈可以看下图:

img

总结

  1. 整理完发现其实整个逻辑代码还挺简单的,但是对于新手而言看见Laravel这种层层叠叠的代码很容易晕掉。
  2. 对于不确定的代码,可以使用PHPStorm搭配XDebug来单步调试,但是这又很容易越陷越深而理不清代码的整个逻辑,这个没啥好办法,只能多看多写多总结。
  3. Laravel确实好用,大而全的框架对于出产品而言真的非常有优势,对于想学习框架的建议可以找些小的框架来看。