TypePHP [With]不可变更新模式:clone-then-update的C风格实践
2026/9/20 21:45:41 网站建设 项目流程

TypePHP #[With]不可变更新模式:clone-then-update的C#风格实践

【免费下载链接】typephpCompile PHP to Native Binaries项目地址: https://gitcode.com/GitHub_Trending/ty/typephp

TypePHP 是一款将 PHP 编译为原生可执行文件的 AOT 编译器,其#[With]编译期属性可以一键生成 C# 风格的不可变更新方法:克隆对象、修改副本并返回副本,让"更新"不再触碰原对象,实现零样板代码的 clone-then-update 实践。

上图即为纯 PHP 源码编译运行的原生程序画面——#[With]生成的方法与手写代码一样,在编译期直接落地为原生机器码,不产生任何反射或分发开销。

为什么需要"不可变更新"?💡

在业务代码里,我们经常想"基于现有对象改一个字段":

$config = new AppConfig(['timeout' => 30]); $prod = new AppConfig(['timeout' => 300]); // 复制粘贴,重复写一遍全部字段

传统的复制粘贴写法有两个痛点:

  • 冗余:字段越多,重复构造越容易漏字段、错字段;
  • 易错:忘记带过某个字段时,新对象悄悄变成默认值,bug 很难排查。

C# 的record类型用with { ... }表达式优雅解决了这个问题。TypePHP 则把同样的思想带进 PHP 世界——只靠一行属性声明。

一行声明,自动生成 with 方法 ✨

给属性加上#[With]属性,TypePHP 在 class lowering 阶段会自动生成一个withXxx()方法,属性声明 src/polyfills.php 中的With就是一个空的编译期标记类:

final class User { #[With] public int $id; #[With] public string $name = 'guest'; } $user = new User(); $user->id = 7; $user->name = 'Alice'; $copy = $user->withId(8); // 自动生成的方法! // $user 不变:id=7 // $copy 是副本:id=8,name 保持 Alice

生成的方法签名是public function withName(T $name): static,其内部逻辑正是clone-then-update 三步走(生成逻辑见 src/Transform/PropertyMethodLowering.php):

  1. 克隆——clone $this拿到一个深层次的独立副本;
  2. 更新—— 只修改副本上被 with 的那个属性;
  3. 返回—— 返回新副本,原对象保持原样。

哪些属性能用#[With]?📋

#[With]的目标与#[Setter]一致:可变实例属性,包括构造器提升属性。以下情况会被编译期直接拒绝:

场景是否可用说明
普通实例属性#[With] public int $value;
构造器提升属性public function __construct(#[With] private int $value)
readonly属性只读属性无法在副本上更新,见 phpunit/code/with-readonly-property.php
带 property hook 的属性hook 会改变读写语义,见 phpunit/code/with-hook-property.php
static 属性#[With]只作用于实例状态

此外,如果类中已显式声明同名withXxx()方法,或生成的方法名冲突、覆盖继承而来的final方法,编译期也会直接报错(参考 phpunit/code/generated-method-final-parent-conflict.php),避免运行期歧义。

与 C#with表达的异同 🔍

对比点C# recordwithTypePHP#[With]
更新方式表达式级,一次可改多个字段方法级,一个方法改一个字段
链式调用可以可以:$u->withId(8)->withName('Bob')
类型安全参数类型继承自属性声明类型
运行时开销装箱/反射(旧版本)零,编译为原生调用

C# 的with更像"改完就扔"的表达式;TypePHP 的方式更贴近 PHP 的方法调用习惯,且每个withXxx()都是强类型的独立方法,可以被类型系统完整推导。需要同时更新多个字段时,链式调用即可:

$final = $user->withId(8)->withName('Bob');

如果你还想让"改"这件事在编译期就被静态拦截,可以配合 TypePHP 的#[Immutable]只读契约使用——它同样零运行时开销,相关设计见 docs/en/IMMUTABLE.md。另外 TypePHP 还支持 PHP 8.5 的clone($obj, [...])语法做一次性多字段更新,参考 phpunit/code/clone-with-codegen.php。

什么时候该用#[With]?🎯

  • 配置 / 参数对象:需要频繁"派生变体"的场景;
  • 函数式风格代码:希望数据流向清晰、原对象不被中途污染;
  • 减少样板:属性一多,手写的clone + 赋值方法会非常啰嗦,#[With]一行顶一段。

完整的编译期属性(#[Getter]#[Setter]#[With]#[Constructor]#[Printer]#[Arrayable])说明,可以参考 README-CN.md 中的"编译期 Attribute 与代码生成"章节(README-CN.md#L368-L417)。

小结 📌

#[With]用 C# 式的 clone-then-update 思想,把 PHP 中繁琐的对象"浅变体"代码压缩成一行属性声明:编译期生成强类型withXxx()方法,克隆、更新、返回一步到位,零运行时开销,且冲突与语义错误都会在编译期暴露。对追求不可变语义又想要原生性能的团队来说,这是 TypePHP 编译期代码生成能力中非常实用的一枚"瑞士军刀"🔧。

【免费下载链接】typephpCompile PHP to Native Binaries项目地址: https://gitcode.com/GitHub_Trending/ty/typephp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询