本文目录导读:

- 核心特征匹配
- 典型的链式调用(Fluent Interface)
- 与经典建造者模式的对比(以 GoF 模式为例)
- 为什么可以这么说?—— 源码层面
- 一个常见的误解:与“链式调用”的区别
- 更典型的例子:Eloquent 的
whereHas和嵌套查询
是的,Laravel 的查询构建器(Illuminate\Database\Query\Builder 及相关的 Eloquent\Builder)严格遵循了建造者模式(Builder Pattern)。
以下是具体的分析和证据:
核心特征匹配
建造者模式的核心目的是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示,Laravel 查询构建器完全符合这个定义:
- 复杂对象:最终生成的 SQL 语句(以及绑定的参数)是一个复杂的字符串和数组组合,包含
SELECT、FROM、WHERE、JOIN、ORDER BY、LIMIT等多个部分。 - 分步构建:通过链式调用
->where(),->orderBy(),->join()等方法,一步步地为 SQL 的各个部分添加条件。 - 最终生成:当调用
->get(),->first(),->insert(),->update(),->delete()或->toSql()时,构建器才将之前设置的所有状态“编译”成最终的 SQL 语句并执行。
典型的链式调用(Fluent Interface)
建造者模式通常伴随着流式接口(Fluent Interface),即每个方法都返回 $this(或构建器自身),允许连续调用。
Laravel 查询构建器是流式接口的典范:
// 建造者模式的典型调用
$users = DB::table('users')
->select('id', 'name', 'email') // 第一步
->where('active', 1) // 第二步
->orderBy('name', 'asc') // 第三步
->limit(10) // 第四步
->get(); // 最终编译并执行
每一步都在修改 Builder 对象的内部状态(如 $this->wheres 数组、$this->orders 数组等),而 get() 方法则是 build() 方法的实际实现。
与经典建造者模式的对比(以 GoF 模式为例)
| 要素 | GoF 建造者模式 | Laravel Query Builder |
|---|---|---|
| Director(指挥者) | 负责控制构建过程的顺序,并调用 Builder 的方法。 | 开发者/调用代码:是你手动确定调用 where、join、orderBy 的顺序。(注:Eloquent ORM 中的 Model 类在某些场景下可以视为一个简化的 Director) |
| Builder(抽象建造者) | 定义构建产品各部件的抽象接口。 | Builder 基类(Illuminate\Database\Query\Builder):定义了 where, orderBy, select 等抽象方法。 |
| ConcreteBuilder(具体建造者) | 实现 Builder 接口,具体构建和装配产品的各个部件。 | 具体的 Builder 实例(如 MySQL、PostgreSQL、SQLite 的 Builder 子类,或 Eloquent\Builder)。 |
| Product(产品) | 最终构建出的复杂对象。 | 生成的 Illuminate\Database\Query\Builder 实例(状态) 或最终编译出的 Illuminate\Database\Query\Grammars\Grammar 对象(即 SQL 语句生成器),产品是“可执行的查询对象”本身。 |
build() 或 getResult() |
返回最终构建完成的产品。 | get(), first(), paginate(), toSql(), getBindings() 等终结方法。 |
为什么可以这么说?—— 源码层面
查看 Laravel 源码,Builder 类内部维护了大量数组来存储构建状态:
// 简化自 Illuminate\Database\Query\Builder
class Builder {
public $columns; // SELECT 的列
public $from; // FROM 表
public $wheres = []; // WHERE 条件
public $orders; // ORDER BY
public $limit; // LIMIT
// ... 更多属性
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
// ... 将条件添加到 $this->wheres 数组
$this->wheres[] = compact('type', 'column', 'operator', 'value', 'boolean');
return $this; // 返回自身,支持链式调用
}
public function get($columns = ['*'])
{
// 调用 compileSelect() 将当前所有状态编译成 SQL
$sql = $this->grammar->compileSelect($this);
// 执行查询
return $this->runSelect($sql, $this->getBindings());
}
}
关键点:get() 方法内部调用了 $this->grammar->compileSelect($this),将当前 Builder 实例(承载所有构建状态)传递给语法编译器,生成最终的 SQL 字符串,这与建造者模式中将构建好的部件组装成最终产品的思路完全一致。
一个常见的误解:与“链式调用”的区别
有人可能会说“这只是链式调用(Fluent Interface),不是建造者模式”。
实际情况是:
- 链式调用是一种接口设计风格(返回
$this)。 - 建造者模式是一种设计模式,它通常使用链式调用来实现,但核心在于分步构建复杂对象并分离构建过程与表示。
Laravel 查询构建器同时具备两者,它使用链式调用作为接口风格,但本质上属于建造者模式,你可以把它看作是“建造者模式在数据库查询场景下的具体实现”。
更典型的例子:Eloquent 的 whereHas 和嵌套查询
Eloquent 的 Builder 扩展了查询构建器,并提供了更高级的建造行为,
User::whereHas('posts', function ($query) {
// 这里传入的是一个子 Builder(闭包作为 Director 的指令)
$query->where('published', 1)->orderBy('created_at');
})->get();
这个闭包接收一个子建造者,进一步构建子查询,完美体现了通过不同指令构建不同查询表示的能力。
| 问题 | 答案 |
|---|---|
| Laravel 查询构建器是建造者模式吗? | 是的,它是建造者模式的典型实现。 |
| 为什么? | 分步构建复杂对象(SQL 查询)。 使用流式接口(链式调用)。 同一套构建过程(调用 where, orderBy 等)可以产生不同的查询(取决于调用顺序和参数)。最终通过一个终结方法( get, first)将状态编译成最终产品(SQL 语句和执行结果)。 |
你完全可以向面试官或同行自信地回答:“是的,Laravel 查询构建器是基于建造者模式实现的,它通过链式调用分步构建查询条件,最终生成并执行 SQL 语句。”