简介
Builder
就是查询到SQL
转换的纽带!
这章很难,劝你放弃阅读 ?♀️?♂️
老板和打工仔的故事
`Eloquent Builder` 是我们在使用 `Laravel`
模型进行查询的时候调用的对象,转换 `SQL` 最终是调用了
`Query Builder` 对象的服务。
所以我们将介绍两个 `Builder` 对象。
复制代码
Query Builder
指Illuminate\Database\Query\Builder
Eloquent Builder
指Illuminate\Database\Eloquent\Builder
这两个对象的关系就像老板和打工仔,上层Eloquent Builder
指挥下层 Query Builder
干活。
查询 User::find(1)
当我们执行这条查询的时候,会触发 Model
的方法
这里不管是否静态调用都没关系,最终会转到 __call
public static function __callStatic($method, $parameters)
{
return (new static)->$method(...$parameters);
}
复制代码
再转发到
public function __call($method, $parameters)
{
// "如果是这两个方法的话会优先调用 Model自身定义的"
if (in_array($method, ['increment', 'decrement'])) {
return $this->$method(...$parameters);
}
// "这里的 $this->newQuery() 就是 Eloquent Builder 对象!"
// "转发调用,实际执行了 $this->newQuery()->{$method}"
return $this->forwardCallTo($this->newQuery(), $method, $parameters);
}
复制代码
首先出场的是
Eloquent Builder
我们来看 $this->newQuery()
获取的是什么!
public function newQuery()
{
return $this->registerGlobalScopes($this->newQueryWithoutScopes());
}
复制代码
继续分析 newQueryWithoutScopes()
public function newQueryWithoutScopes()
{
return $this->newModelQuery()
->with($this->with)
->withCount($this->withCount);
}
复制代码
public function newModelQuery()
{
return $this->newEloquentBuilder(
$this->newBaseQueryBuilder()
)->setModel($this);
}
复制代码
public function newEloquentBuilder($query)
{
return new Builder($query);
}
// "Builder 的构造方法声明"
public function __construct(QueryBuilder $query)
{
$this->query = $query;
}
复制代码
返回一个 Query Builder
对象
protected function newBaseQueryBuilder()
{
$connection = $this->getConnection();
return new QueryBuilder(
$connection, $connection->getQueryGrammar(), $connection->getPostProcessor()
);
}
复制代码
其实经过上面一系列的操作最主要的目的就是将 Query Builder
赋值给 Eloquent Builder
可见 Eloquent Builder
并没有构建 SQL
语句的能力
但是这层封装使得 Eloquent Builder
拥有了这能力。
所以真正的构建服务还是来自 Query Builder
对象。
经过上面的分析我们回到最开始的调用处
public function __call($method, $parameters)
{
// "如果是这两个方法的话会优先调用 Model自身定义的"
if (in_array($method, ['increment', 'decrement'])) {
return $this->$method(...$parameters);
}
// "这里的 $this->newQuery() 就是 Eloquent Builder 对象!"
// "转发调用,实际执行了 $this->newQuery()->{$method}"
return $this->forwardCallTo($this->newQuery(), $method, $parameters);
}
复制代码
所以我们对模型层的大部分调用都是调用 (Eloquent Builder)>{$method}
那么就从开篇的例子开始分析这个 Eloquent Builder
到底有什么方法!
Find(1)
方法解析
public function find($id, $columns = ['*'])
{
if (is_array($id) || $id instanceof Arrayable) {
return $this->findMany($id, $columns);
}
return $this->whereKey($id)->first($columns);
}
复制代码
我们传入的是一个 Int
,直接分析 $this->whereKey($id)->first($columns)
public function whereKey($id)
{
if (is_array($id) || $id instanceof Arrayable) {
$this->query->whereIn($this->model->getQualifiedKeyName(), $id);
return $this;
}
// "从这里开始分析"
// "$this->model->getQualifiedKeyName() 就是获取主键的名字是什么,就不赘述"
return $this->where($this->model->getQualifiedKeyName(), '=', $id);
}
复制代码
️?继续看,接下来就是重点了!关于查询构建器是如何构建 SQL
的。
我们在脑海里面先想一下,查询构建器是干啥的?!
回忆下是不是很久没有写原生 SQL
了?还记得 SELECT * FROM users WHERE id = 1;
吗,在 Laravel
中查询构建器功能就是将我们的 User::find(1)
转化成上面的 SQL
好了,我们回来继续分析如何完成这个转化!
打工仔现身
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
if ($column instanceof Closure) {
$column($query = $this->model->newModelQuery());
$this->query->addNestedWhereQuery($query->getQuery(), $boolean);
} else {
$this->query->where(...func_get_args());
}
return $this;
}
复制代码
执行这里的代码,这里调用了 打工仔 Query Builder
$this->query->where(...func_get_args());
复制代码
展开打工仔的 where
, 接收的参数就是上面完完整整的转发了一次。
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
if (is_array($column)) {
return $this->addArrayOfWheres($column, $boolean);
}
[$value, $operator] = $this->prepareValueAndOperator(
$value, $operator, func_num_args() === 2
);
if ($column instanceof Closure) {
return $this->whereNested($column, $boolean);
}
if ($this->invalidOperator($operator)) {
[$value, $operator] = [$operator, '='];
}
if ($value instanceof Closure) {
return $this->whereSub($column, $operator, $value, $boolean);
}
if (is_null($value)) {
return $this->whereNull($column, $boolean, $operator !== '=');
}
if (Str::contains($column, '->') && is_bool($value)) {
$value = new Expression($value ? 'true' : 'false');
}
$type = 'Basic';
$this->wheres[] = compact(
'type', 'column', 'operator', 'value', 'boolean'
);
if (! $value instanceof Expression) {
$this->addBinding($value, 'where');
}
return $this;
}
复制代码
上面这么一大堆的代码实在是懒得讲了~看图吧,
反正就是对 Builder
这几个圈起来的属性赋值
仔细看看,反正没什么难的,就是先把数据丢到这些成员里存起来。
上面我们存好了数据,那么后面我们就要想办法从这些属性中构建处 SQL
了,别急,我们现在开始。
执行查询
回到刚才开始的地方
public function find($id, $columns = ['*'])
{
if (is_array($id) || $id instanceof Arrayable) {
return $this->findMany($id, $columns);
}
// "刚才执行了这句"
return $this->whereKey($id)->first($columns);
}
复制代码
打工仔兄弟 Illuminate\Database\Concerns\BuildsQueries
现身
这里的 first()
方法是 use
这 BuildsQueries
这个特质类
public function first($columns = ['*'])
{
return $this->take(1)->get($columns)->first();
}
复制代码
追进去这里要注意 $this
这里指向的是 Eloquent Builder
对象 ,
源码里面是没有 take
这个方法,这又是通过 __call
方法来调用
最终执行代码就是 $this->query->take(1)->get($columns)->first()
这里关于为什么这样执行的可以查阅 Eloquent Builder
魔术方法。
接着来
public function take($value)
{
// "就是赋值操作,给对象的 $this->limit = $value;"
return $this->limit($value);
}
复制代码
继续看,准备好秋名山最后几个关卡来了
// "这个 `get`方法是老板 `Eloquent Builder` 中定义的"
public function get($columns = ['*'])
{
$builder = $this->applyScopes();
if (count($models = $builder->getModels($columns)) > 0) {
$models = $builder->eagerLoadRelations($models);
}
return $builder->getModel()->newCollection($models);
}
复制代码
接着重点是 $builder->getModels($columns)
获取数据的操作
public function getModels($columns = ['*'])
{
return $this->model->hydrate(
$this->query->get($columns)->all()
)->all();
}
复制代码
我们不理会其他,只看 $this->query->get($columns)->all()
这里就是调用打工仔 Query Builder
的 get()
public function get($columns = ['*'])
{
// "onceWithColumns 这个方法没什么好分析,接收两个参数,返回第二个参数(闭包)"
return collect($this->onceWithColumns($columns, function () {
return $this->processor->processSelect($this, $this->runSelect());
}));
}
复制代码
继续看闭包里面 $this->processor->processSelect($this, $this->runSelect());
public function processSelect(Builder $query, $results)
{
// "这里没干啥,就是把 $results 返回"
return $results;
}
复制代码
那么最重点的来了,看名字就是运行 SQL
$this->runSelect()
复制代码
这里的
$this->connection->select()
是驱动层提供对接MySQL
的调用,我们不用关心啦~我们看到这里的select
有三个参数,第一个就是我们苦苦寻找的SQL
,第二个是PDO
参数绑定的数据。
protected function runSelect()
{
return $this->connection->select(
$this->toSql(), $this->getBindings(), ! $this->useWritePdo
);
}
复制代码
toSql()
tips 我们平时在使用
(new User)->getQuery()->toSql();
可以看到预编译的SQL
public function toSql()
{
return $this->grammar->compileSelect($this);
}
...
// "方便阅读合并了部分源码"
public function compileSelect(Builder $query)
{
if ($query->unions && $query->aggregate) {
$column = $this->columnize($aggregate['columns']);
if ($query->distinct && $column !== '*') {
$column = 'distinct '.$column;
}
$sql = 'select '.$aggregate['function'].'('.$column.') as aggregate';
$query->aggregate = null;
$sql = $sql.' from ('.$this->compileSelect($query).') as '.$this->wrapTable('temp_table');
}
$original = $query->columns;
if (is_null($query->columns)) {
$query->columns = ['*'];
}
$sql = trim($this->concatenate(
$this->compileComponents($query))
);
$query->columns = $original;
return $sql;
}
复制代码
这一坨坨代实在讲起来没有味道,就是各种判断,然后抽取属性拼接成字符串。
这里面有兴趣可以自行研究,这篇仅仅介绍执行逻辑。
总结
老板
Eloquent Builder
和打工仔Query Builder
的职责!
Builder
的原理,先存入属性,在执行toSql()
其他功能等待读者开发!