Laravel 5.x:Eloquent模型为何可作为QueryBuilder使用?
嘿,这个问题问到点子上了!我刚接触Laravel的时候也琢磨了好久——明明看Illuminate\Database\Eloquent\Model和Illuminate\Database\Query\Builder既没继承关系也没实现同一个接口,为啥能直接在模型上调用orderBy()、where()这些Builder的方法?
其实核心是PHP魔术方法+Eloquent专属的Builder封装,下面一步步给你拆解:
1. 先搞清楚两个Builder的关系
你可能漏看了一个关键类:Illuminate\Database\Eloquent\Builder(咱们叫它「Eloquent Builder」)。这个类是专门为模型设计的查询构建器,它直接继承了你提到的Illuminate\Database\Query\Builder(底层查询构建器)。也就是说,Eloquent Builder拥有底层Query Builder的所有方法,还额外加了模型相关的逻辑(比如关联查询、软删除、全局作用域这些)。
2. 魔术方法帮你转发调用
当你在模型上调用一个不存在的方法时(不管是静态调用还是实例调用),Laravel会通过PHP的魔术方法把调用转发给Eloquent Builder实例:
静态调用场景(比如ModelObject::orderBy()->get())
模型类里的__callStatic方法会先创建一个模型实例,然后把方法调用转发给实例的同名方法:
public static function __callStatic($method, $parameters) { return (new static)->$method(...$parameters); }
实例调用场景(比如$model->where()->first())
模型的__call方法会直接把调用转发给newQuery()返回的Eloquent Builder实例:
public function __call($method, $parameters) { if (in_array($method, ['increment', 'decrement'])) { return $this->$method(...$parameters); } return $this->forwardCallTo($this->newQuery(), $method, $parameters); }
3. newQuery()是关键入口
上面提到的newQuery()方法,会帮你创建并返回一个Eloquent Builder实例,同时把当前模型和这个Builder绑定起来:
public function newQuery() { return $this->newQueryWithoutScopes(); } public function newQueryWithoutScopes() { $builder = $this->newEloquentBuilder( $this->newBaseQueryBuilder() ); // 绑定当前模型到Builder,设置表名等信息 return $builder->setModel($this)->with($this->with); }
举个完整流程例子
比如你执行ModelObject::orderBy('created_at', 'asc')->get():
- 调用
ModelObject上不存在的静态方法orderBy,触发__callStatic - 创建
ModelObject实例,调用实例的orderBy方法 - 实例的
orderBy不存在,触发__call,调用newQuery()得到Eloquent Builder实例 - 把
orderBy方法调用转发给这个Eloquent Builder实例(它继承了底层Query Builder,所以有这个方法) - 最后调用
get(),同样是转发给Builder,执行查询并返回结果集
说白了,模型就像一个「门面」,通过魔术方法把所有查询相关的操作都交给了背后的Eloquent Builder来处理,而Eloquent Builder本身就是底层Query Builder的增强版,所以你能无缝调用所有查询构建器的方法。
内容的提问来源于stack exchange,提问作者Steven Armstrong

