Laravel查询构建器是建造者模式吗

wen PHP项目 1

本文目录导读:

Laravel查询构建器是建造者模式吗

  1. 核心特征匹配
  2. 典型的链式调用(Fluent Interface)
  3. 与经典建造者模式的对比(以 GoF 模式为例)
  4. 为什么可以这么说?—— 源码层面
  5. 一个常见的误解:与“链式调用”的区别
  6. 更典型的例子:Eloquent 的 whereHas 和嵌套查询

是的,Laravel 的查询构建器(Illuminate\Database\Query\Builder 及相关的 Eloquent\Builder严格遵循了建造者模式(Builder Pattern)

以下是具体的分析和证据:

核心特征匹配

建造者模式的核心目的是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示,Laravel 查询构建器完全符合这个定义:

  • 复杂对象:最终生成的 SQL 语句(以及绑定的参数)是一个复杂的字符串和数组组合,包含 SELECTFROMWHEREJOINORDER BYLIMIT 等多个部分。
  • 分步构建:通过链式调用 ->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 的方法。 开发者/调用代码:是你手动确定调用 wherejoinorderBy 的顺序。(注: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 语句。”

抱歉,评论功能暂时关闭!