PHPStan 错误标识符 `property.readOnly` 全解析:readonly 属性覆盖可读写父类属性时的 LSP 冲突与修复
开发工具代码质量静态分析【免费下载链接】phpstanPHP Static Analysis Tool - discover bugs in your code without running it!项目地址https://gitcode.com/gh_mirrors/ph/phpstan点击查看免费下载本篇指南围绕 PHPStan 错误标识符property.readOnly展开讲解其在当前开源仓库 PHPStanPHP Static Analysis Tool 中何时被触发、背后的 PHP 语言语义与里氏替换原则LSP冲突以及两种官方推荐的修复方案。读者读完本文后能够准确识别子类用readonly属性覆盖父类可读写属性的反模式掌握保持类型契约一致性的实战修正方法并理解property.readOnly与同族 readonly 错误标识符之间的区别。错误标识符速览property.readOnly是 PHPStan 错误标识符体系Error Identifier中的一员。它针对的是 PHP 类属性声明本身其文档位于 website/errors/property.readOnly.md该文档的 frontmatter 给出了该标识符的元数据--- title: property.readOnly shortDescription: Readonly property overrides a readwrite property from the parent class. ignorable: false ---title错误标识符的名称即property.readOnly。shortDescription一句话概括触发条件——子类中的 readonly 属性覆盖了父类中的可读写readwrite属性。ignorablefalse表示该错误不可忽略。按照 website/errors/CLAUDE.md 中记录的约定ignorable: false对应规则构建链中使用了-nonIgnorable()的情形或以phpstan.、phpstanPlayground.开头的标识符意味着此类问题不能通过常规的ignoreErrors配置掩盖必须真正修正代码。在 PHPStan 输出的错误报告中每个错误都带有标识符形如Line 6: Readonly property Child::$value overrides readwrite property Base::$value. ── property.readOnly标识符是错误信息中的稳定机器可读标签配合--error-format或编辑器集成可用于精确定位与统计一类问题。触发示例最小复现代码原文档给出了一个最小化的 PHP 示例能够稳定触发property.readOnly?php declare(strict_types 1); class Base { public mixed $value; } class Child extends Base { public readonly mixed $value; }核心特征有两点父类Base声明了一个可读写readwrite属性$value子类Child用readonly修饰符覆盖了同名属性$value。在 PHP 8.1 起引入的 readonly 属性语义下readonly属性只能在初始化时赋值一次此后不可再写入。子类这一覆盖行为把父类可读写的契约收窄成了只读。为什么会被报告LSP 契约破坏原文档明确指出该错误的本质是违反里氏替换原则Liskov Substitution Principle, LSPAreadonlyproperty in a child class is overriding a readwrite property in the parent class. The parent class allows both reading and writing, but the child class restricts writing. This violates the Liskov Substitution Principle because code written against the parent type expects to be able to assign the property.也就是说子类Child是父类Base的子类型任何针对Base类型编写的代码都假定可以读取并写入$value。当Child实例被当作Base使用多态替换时写入操作会在运行时抛出ErrorPHP 对 readonly 属性二次赋值会抛出Error: Cannot modify readonly property。子类通过readonly收窄了父类公开的写入能力破坏了子类必须能够替换父类且不改变程序行为的契约。从 PHP 语言语义角度看这也是一个真实可运行的运行时风险而不仅仅是静态分析层面的洁癖PHP 允许声明这种覆盖readonly与普通属性的覆盖在语言层面是合法的但在运行时任何基于父类类型签名编写的赋值代码遇到Child实例都会直接报错。PHPStan 的作用就是在运行之前把这个隐患暴露出来。更隐蔽的变体readonly修饰符的冲突不止出现在直接 extends 的场景。从 website/errors/ 目录中同族标识符可见PHPStan 会系统性地检查 readonly 属性的各类非法使用方式例如property.readOnlyAssignNotInConstructor.mdreadonly 属性在构造函数之外被赋值property.readOnlyAssignNotOnThis.mdreadonly 属性不是通过$this-在声明类内部赋值property.readOnlyAssignOutOfClass.mdreadonly 属性在声明类外部被赋值property.readOnlyNoNativeType.mdreadonly 属性缺少原生类型声明readonly 属性必须有类型property.readOnlyDefaultValue.mdreadonly 属性带有默认值不允许property.readOnlyInInterface.md接口中声明了 readonly 属性不允许assign.readOnlyProperty.mdreadonly 属性被赋值多次。其中assign.readOnlyProperty与本文主题最接近但角度不同它关注的是单个 readonly 属性在构造流程中被赋值多次而property.readOnly关注的是继承层级中可写契约被收窄。两者分别对应 readonly 属性只能初始化一次和不能覆盖可写父类属性两条独立约束。如何修复两种方向原文档给出了两种修复方向分别对应子类让步与父类对齐两种契约调整策略。方案一移除子类的 readonly 修饰符匹配父类契约如果属性在父类中就是可读写的子类应保持一致去掉readonly?php declare(strict_types 1); class Base { public mixed $value; } class Child extends Base { - public readonly mixed $value; public mixed $value; }这是最直接的修复子类不再收窄父类契约针对Base编写的赋值代码在任何子类型上都能正常工作LSP 冲突消除。方案二父类也声明为 readonly让整个层级只读如果业务上该属性本就应该是只读的例如依赖注入的配置项、不可变值对象那么应当让父类同步声明readonly使契约在整个继承层级中一致收窄?php declare(strict_types 1); class Base { - public mixed $value; public readonly mixed $value; } class Child extends Base { public readonly mixed $value; }注意当父类属性为readonly时子类覆盖该属性必须也使用readonly修饰符这是 PHP 语言的硬性要求——因此方案二的改写同时满足了两层约束语言层面合法静态分析层面不再冲突。如何选择若属性在运行时确实会被重新赋值可变状态选方案一去掉子类的readonly若属性是初始化后不可变的不可变对象、配置快照选方案二并确保所有赋值都发生在声明类内部通常位于构造函数或__clone()中。两种方案的共同原则是继承层级中的属性读写契约必须一致不允许子类单方面收窄父类的公开能力。深入理解PHP readonly 属性约束全景要彻底理解property.readOnly需要把 PHP readonly 属性的完整约束装进头脑。readonly 属性自 PHP 8.1 引入其约束包括只能赋值一次初始化后的再次赋值会抛出运行时Error初始化位置受限赋值只能在声明它的类内部、且通过$this-进行通常即构造函数必须声明类型readonly属性不能缺少原生类型声明不能有默认值readonly属性不能带默认值因为没有类型声明之外的初始化入口不能用于接口接口中的属性不能声明为readonly克隆时重新初始化PHP 8.3自 PHP 8.3 起readonly属性可以在__clone()中重新初始化但每个克隆过程仍只能赋值一次。property.readOnly是上述约束在继承场景下的延伸即使子类属性的赋值行为完全合规例如在子类构造函数中只赋值一次只要它覆盖了父类一个可写属性就破坏了针对父类类型编写的外部代码的假设。这也是该错误标识符ignorable: false的原因——它代表的是设计层面的契约破坏无法通过忽略配置来规避。在项目中定位与排查在实际项目中遇到property.readOnly时建议按以下步骤排查定位报告行PHPStan 会在覆盖发生的属性声明行上报告错误在示例中是Child::$value的声明行查看继承链向上查找父类或接口、trait中同名属性的声明确认其是否可读写确认运行时行为判断该属性在真实业务中是否会被重新赋值。若会选方案一若不会选方案二并把父类的契约一并改掉检查同族问题修复后运行 PHPStan 全量扫描确认没有连带触发 assign.readOnlyProperty 等其他 readonly 相关标识符。需要注意本仓库镜像未包含 PHPStan 的src/完整源码目录因此上述关于规则实现细节的说明主要依据 website/errors/CLAUDE.md 中记录的标识符文档生成规范与 website/errors/ 目录下的各标识符文档推断得出从这些文档的 frontmatter 与结构看每个错误标识符文档统一采用 Code example → Why is it reported? → How to fix it 三段式结构property.readOnly即遵循这一标准模板。延伸阅读错误标识符文档规范了解 PHPStan 错误标识符文档的生成方式、frontmatter 字段含义title、shortDescription、ignorable与撰写约定assign.readOnlyProperty.mdreadonly 属性重复赋值的检测逻辑与修复示例含 PHP 8.3__clone()重新初始化场景property.readOnlyAssignNotInConstructor.md 与 property.readOnlyNoNativeType.mdreadonly 属性其他常见违规形式的参考README.mdPHPStan 项目概览与基本使用方式。赞分享开发工具代码质量静态分析【免费下载链接】phpstanPHP Static Analysis Tool - discover bugs in your code without running it!项目地址https://gitcode.com/gh_mirrors/ph/phpstan点击查看免费下载相关推荐PHPStan 错误标识符 property.readWrite 详解可写属性覆盖父类 readonly 属性的检测与修复PHPStan 错误标识符 property.readWrite 详解可写属性覆盖父类 readonly 属性的检测与修复 本篇技术指南围绕 PHPStan开发工具代码质量静态分析PHPStan 错误标识符 property.nonStatic子类非静态属性覆盖父类静态属性时的静态性冲突检测PHPStan 错误标识符 property.nonStatic子类非静态属性覆盖父类静态属性时的静态性冲突检测 本篇技术指南讲解 PHPStan 错误标识符开发工具代码质量静态分析Wagtail 嵌入式内容Embeds完全指南oEmbed、自定义 Finder 与 Embed 模型详解Wagtail 嵌入式内容Embeds完全指南oEmbed、自定义 Finder 与 Embed 模型详解 Wagtail 作为一款以灵活性和用户体验为核开发工具代码质量静态分析上一篇Honcho混合搜索指南BM25向量检索如何让AI精准找到记忆下一篇猫抓Cat-Catch浏览器资源嗅探终极指南轻松下载网页视频音频创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

STM32CubeIDE与ST-LINK下载程序全攻略:从接线到固件更新排查

STM32CubeIDE与ST-LINK下载程序全攻略:从接线到固件更新排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/24 15:43:01 阅读更多 →
科大讯飞语音链路 Demo:从唤醒到播报的完整抽离

科大讯飞语音链路 Demo:从唤醒到播报的完整抽离

在一个桌面类 Android 项目里,语音助手经常会越写越“粘”:首页要显示状态,悬浮窗要同步状态,业务指令要启动系统设置,通话页面又要暂停麦克风。最后真正想复用的那条链路,反而被 Launcher 业务包住了。 一…

2026/9/24 15:43:01 阅读更多 →
2026年杭州亨得利腕表质保政策与官方维保受理门店地址、电话(官方更新版)

2026年杭州亨得利腕表质保政策与官方维保受理门店地址、电话(官方更新版)

随着腕表维保需求持续增长,广大表主对于腕表质保权益、官方维保受理渠道的关注度不断提升,2026年亨得利更新杭州区域腕表质保相关政策,同步公示杭州亨得利服务中心受理门店地址、联络电话,帮助杭州及周边地区表主清晰了解自身腕表…

2026/9/24 15:43:00 阅读更多 →

最新新闻

深入理解 Sinon 的 `spyCall.firstArg`:读取单次调用首个参数的正确姿势

深入理解 Sinon 的 `spyCall.firstArg`:读取单次调用首个参数的正确姿势

测试开发工具 【免费下载链接】sinon Test spies, stubs and mocks for JavaScript. 项目地址: https://gitcode.com/gh_mirrors/si/sinon 点击查看 免费下载 spyCall.firstArg 是 Sinon 中 spy call 对象的一个核心只读属性,用于获取某一次函数调用传入…

2026/9/25 4:57:52 阅读更多 →
腾讯云WorkBuddy Enterprise企业级AI Agent平台架构与实操指南

腾讯云WorkBuddy Enterprise企业级AI Agent平台架构与实操指南

1. 从零理解 WorkBuddy Enterprise 的定位与核心价值1.1 这个平台到底解决什么问题WorkBuddy Enterprise 是腾讯云推出的一套企业级 AI 平台与 Agent 生态产品。说白了,它要解决的核心问题是:企业想用 AI,但不知道怎么把 AI 能力安全、可控、…

2026/9/25 4:57:52 阅读更多 →
Endnote在Word中消失?COM加载项排查与修复指南

Endnote在Word中消失?COM加载项排查与修复指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 4:57:52 阅读更多 →
Java图书管理系统SWT实战:从环境搭建到避坑指南

Java图书管理系统SWT实战:从环境搭建到避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 4:57:52 阅读更多 →
GDS版图从入门到精通:层次结构、生成流程与-uniquifycellnames避坑指南

GDS版图从入门到精通:层次结构、生成流程与-uniquifycellnames避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 4:57:52 阅读更多 →
Navicat免安装版深度解析:依赖库、配置与MySQL连接排查指南

Navicat免安装版深度解析:依赖库、配置与MySQL连接排查指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 4:56:51 阅读更多 →

日新闻

AI元人文:从工具使用到思维重构的深度探索

AI元人文:从工具使用到思维重构的深度探索

最近半年我一直在琢磨一件事:AI元人文到底是什么?说白了,就是“用元视角重新审视人与AI的关系”,也在“探索AI如何反向逼着我们发现自己的思考边界”。标题里的“元探索”,在我看就是一层套一层的追问——当你用AI解决…

2026/9/25 0:00:41 阅读更多 →
Python+CNN车牌识别实战:从数据预处理到模型训练与部署

Python+CNN车牌识别实战:从数据预处理到模型训练与部署

简介:基于Python与卷积神经网络的车牌识别项目,面向计算机视觉初学者及智能交通开发者,目标是帮助用户掌握从数据预处理、模型构建到实际部署的完整流程。压缩包共25个文件,包含jpg/png图像样本、py训练脚本、md说明文档、dat数据…

2026/9/25 0:00:41 阅读更多 →
Vim基础操作全攻略:保存退出、模式切换与高频命令实战

Vim基础操作全攻略:保存退出、模式切换与高频命令实战

1. 项目概述1.1 核心需求解析今天聊聊Vim。写这个题目的原因是:几乎每个后端开发者、运维人员、数据工程师某天都会遇到一个场景——深夜加班,服务器登录界面只有黑底白字,编辑器只有vi/vim,你必须在五分钟内完成一次配置修改并保…

2026/9/25 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/24 14:34:13 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/24 9:10:42 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/24 14:33:56 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/24 12:50:34 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/24 14:33:48 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/24 12:49:17 阅读更多 →