php建造者模式核心是build()锁死必填校验、每个setter必须return $this、字段预处理不延迟;链式调用报null错误主因是setter漏return或静默返回;build()须终验必填字段、生成唯一编号、避免重复解析,且返回新实例而非$this。

直接说结论:PHP里实现建造者模式,核心不是写多少类,而是把“必填校验锁死在 build()”、每个 setter 必须 return $this、字段预处理不延迟到构建最后一刻。
为什么链式调用突然报 Call to a member function setItems() on null
这不是语法错误,是 PHP 对方法链的硬性要求没满足。常见原因只有两个:
-
setCustomer()方法末尾漏了return $this;,导致下个方法调用时操作的是null - 在
setAmount()里做了类型转换(比如$this->amount = (float) $value;),但没写return $this;,链就断了 - 更隐蔽的是:某个
setter里写了if (!is_email($value)) { return; }—— 静默返回null,而不是抛异常,结果调用方完全不知道失败了
build() 里必须做哪些事,不能省
build() 是唯一能信任的终验入口,所有不可绕过的业务规则必须集中在这里判断。别指望 setter 做校验,也别在构造函数里塞逻辑。
- 检查必填字段是否已设置且类型合法:
if (!isset($this->customer) || !($this->customer instanceof Customer)) { throw new InvalidArgumentException('customer is required'); } - 编号生成只在此处执行一次:
$this->orderNumber = 'ORD-' . date('Ymd') . '-' . str_pad((string) $this->nextId(), 6, '0', STR_PAD_LEFT);,避免多次调用build()产生重复号 - 不做重复解析:如果
setAddress()已接受Address对象并存为$this->address,build()就不该再对原始字符串做json_decode()或explode(',') - 不返回未冻结对象:构建完立刻返回新
Order实例,不要返回$this或允许后续再调withXxx()
要不要把 Builder 写成静态内部类?
取决于你能不能改目标类源码,以及团队对命名污染的容忍度。
- 目标类是你自己写的、且不会被继承(加了
final)→ 推荐用静态内部类:public static function builder(): self,调用干净:Order::builder()->setCustomer($c)->build() - 目标类来自第三方 SDK 或你无权修改 → 只能写独立类,比如
OrderBuilder,否则无法注入依赖或覆盖构造逻辑 - 项目里已有大量
new Order(...)调用 → 别急着重构整个 Builder,先从高频、高错率场景切入(比如创建支付订单),避免一次性替换引发隐藏状态问题
最容易被忽略的一点:Builder 不是万能胶。如果对象字段之间存在强时序依赖(比如“先设地址才能算运费,运费影响最终金额”),那光靠链式 setter 无法表达这种因果关系——这时候该上 Director 控制流程,或者干脆拆成两阶段构建(草稿 → 校验后提交),而不是硬塞进一个 build() 里强行闭环。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











