当前位置:首页 > 文章列表 > 文章 > php教程 > PHP readonly 对象适合配置值还是领域实体

PHP readonly 对象适合配置值还是领域实体

来源:17golang原创 2026-10-08 21:53:13 0浏览 收藏

PHP readonly 对象更适合配置快照和值对象,而不是默认用来承载会不断改变状态的领域实体。判断标准不是“这个类重要不重要”,而是它创建后是否应该保持同一组值:连接配置、金额、日期范围、标识符通常适合;订单、购物车、工单这类有身份和生命周期的实体,通常保留可变状态更自然,再把其中的金额、编号等局部类型设计成 readonly 值对象。

PHP 官方手册:https://www.php.net/manual/en/language.oop5.basic.php

选型结论
  • 创建后只读取、修改时整体替换:优先 readonly class。
  • 需要 pay()、cancel() 等状态迁移:实体本身通常不要整体只读。
  • 属性里保存普通对象时只是“引用不能替换”,对象内部仍可能变化。
  • PHP 8.2 开始支持只读类;PHP 8.3 可在 __clone() 中重新初始化只读属性以完成深克隆。

先确认 readonly class 真正限制了什么

从 PHP 8.2 起,类可以声明为 readonly。它会让所有实例属性都具有只读约束,并禁止动态属性。类中的属性必须有类型,不能声明静态属性;属性初始化后不能再次赋值、递增、通过引用修改或 unset。

timeoutSeconds = 30;

只读类还限制继承关系。编写库代码时,不要只考虑当前类能否工作,还要考虑调用方是否需要通过子类增加可变属性。对稳定的小类型,这个限制通常有利;对扩展点较多的框架基类,它可能过早锁死设计。

配置和值对象为什么更适合 readonly

配置对象的职责通常是把一组已经解析、校验过的参数交给服务使用。业务运行期间如果要改变配置,更清晰的方式是重新生成一个新对象并替换旧快照,而不是让多个组件共享一个会被悄悄修改的实例。

HttpClientConfig 配置快照和 Order 领域实体的静态组成对比
图1:配置快照与领域实体的静态组成对比,不是运行截图。
baseUrl, $seconds, $this->retryPolicy);
    }
}

withTimeout() 的优点是调用点能看出“创建新值”,测试也不必担心其他对象持有的配置被联动修改。金额、邮箱地址、坐标、日期范围和业务编号同样符合这个特征:相等性主要由值决定,修改意味着得到另一个值。

readonly 是属性槽只读,不是递归冻结

最容易误判的地方是对象属性。只读属性保存一个对象时,PHP 限制的是属性槽不能改绑到另一个对象;被引用对象的内部状态仍可能变化。官方手册把这种情况称为内部可变性。

readonly UserView 的属性槽与 Address 对象内部状态之间的静态边界
图2:readonly 属性槽与对象内部状态的静态边界说明图,不是运行证据。
roles[] = 'admin';

// Address 引用没有被替换,但普通对象内部仍可修改。
$view->address->city = 'Shanghai';

因此,如果配置对象需要真正接近深度不可变,它引用的子对象也应设计成 readonly,或使用 DateTimeImmutable 这类不可变类型。仅把最外层类加上关键字,不能自动冻结整张对象图。

PHP 8.3 增加了一个实用边界:在 __clone() 中可以重新初始化只读属性,便于克隆时把内部对象也克隆一份。这个能力适合“复制后调整”的值对象,但依然要明确哪些子对象需要深克隆,不能把普通 clone 当成自动深复制。

有身份和生命周期的领域实体通常保留可变状态

领域实体与值对象的差别在于身份和生命周期。订单即使状态从“待支付”变成“已支付”,仍然是同一个订单。若把整个 Order 声明为只读,每次状态变化都要创建新实例,还要重新处理 ORM 跟踪、事件、关联集合和并发版本,成本往往高于收益。

status !== OrderStatus::Pending) {
            throw new DomainException('当前订单不能支付');
        }
        if ($received != $this->total) {
            throw new DomainException('支付金额不匹配');
        }
        $this->status = OrderStatus::Paid;
    }
}

这是一种更实用的组合:实体的 status 可以在受控方法内变化,身份和金额使用只读值对象,外部仍不能直接篡改关键属性。它既保留领域行为,也把真正不该变化的部分锁住。

什么时候领域实体也可以整体 readonly

整体只读并不是绝对错误。如果项目采用事件溯源、函数式建模,或实体更新本来就通过“旧对象 + 事件 = 新对象”完成,那么返回新实例可能正是设计目标。只要持久化层、集合关联和并发控制都按不可变模型实现,readonly 可以帮助语言层表达约束。

采用前至少回答四个问题:

  • 状态变化是否天然返回新实例,而不是依赖 ORM 脏检查?
  • 关联对象是否同样不可变,还是会造成浅只读错觉?
  • 新实例如何保留同一业务身份和并发版本?
  • 事件发布、序列化和缓存键是否能正确处理对象替换?

如果这些问题没有明确答案,优先选“可变实体 + readonly 值对象”,通常更容易与现有 PHP 框架和 ORM 协作。

选型速查表

对象类型整体 readonly推荐方式
环境配置、HTTP 客户端参数适合构造时校验,变更时生成新快照
Money、OrderId、Email、DateRange适合让值对象自校验并保持不可变
DTO、查询结果视图通常适合注意内部对象的浅只读问题
Order、Cart、Ticket 等实体通常不适合用行为方法控制状态,把关键字段做成 readonly 值对象
事件溯源中的不可变聚合快照可以确认持久化、版本和关联都支持对象替换
框架扩展基类谨慎先评估继承、Trait 属性和扩展需求

常见误区

readonly 等于 const 对象吗?

不完全等同。它约束属性初始化后的重新赋值,但对象属性指向的普通对象仍可能修改内部状态,所以不是递归常量。

readonly 属性可以保存数组吗?

可以,但初始化后不能追加、删除或修改数组元素,因为这些操作属于对只读属性的间接修改。

readonly class 可以有方法吗?

可以。方法可以执行计算、校验并返回新对象,只是不能在初始化完成后重新写入只读属性。

使用 readonly 后还需要 private 吗?

仍然需要按封装目标选择可见性。只读解决“能否重新赋值”,可见性解决“谁能读取或在允许的初始化作用域内设置”。两者职责不同。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
GitHub Actions 安全指南为何持续收紧工作流权限GitHub Actions 安全指南为何持续收紧工作流权限
上一篇
GitHub Actions 安全指南为何持续收紧工作流权限
Go 1.27 泛型方法如何为容器补上链式转换能力
下一篇
Go 1.27 泛型方法如何为容器补上链式转换能力
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    381次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    451次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    463次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    403次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    232次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码