PHP 8.4 升级是“不改就崩”,因 readonly 属性继承被禁、动态属性访问升为 ValueError、属性钩子语法仅 8.4 支持,且类型化常量和 #[Override] 在 8.3 已有严格校验。

PHP 8.3 和 8.4 的兼容性差异不是“能不能跑”,而是“不改就崩”和“改了才稳”的区别——8.4 引入的几项语言原语直接改变了类定义的语义边界,尤其在继承、属性初始化和动态行为上,已有代码很可能在升级后立即报错或静默失效。
readonly 属性继承被严格禁止
PHP 8.4 明确禁止子类重新声明从父类继承的 readonly 属性,而 8.3 中这类写法可能侥幸通过(尤其在未显式初始化时)。
- 8.3 允许:父类声明
protected readonly string $id,子类再写一遍public readonly string $id(虽不推荐,但不报错) - 8.4 立即 Fatal error:Cannot override readonly property
Child::$id - 影响范围:所有使用 DTO 继承链、ORM 实体抽象基类、或自定义只读封装的项目
- 修复方式:删掉子类中重复的
readonly声明,改用构造函数参数传递或受保护的初始化逻辑
动态属性访问默认触发 ValueError
PHP 8.3 已将未声明属性访问标记为 E_DEPRECATED,但 8.4 升级为 ValueError —— 这是运行时异常,无法被 @ 抑制,且不会进入 __get/__set。
- 常见崩溃点:
$obj->undefined_field = 'x'或echo $obj->missing; - 8.3 行为:仅警告,程序继续执行(若错误报告未开启则静默)
- 8.4 行为:直接抛出
ValueError,除非类加了#[AllowDynamicProperties] - 注意:
#[AllowDynamicProperties]不能用于readonly class,二者互斥
属性钩子与非对称可见性无回退路径
public string $name { get => ..., set => ... } 和 public private(set) string $slug 是 PHP 8.4 独占语法,8.3 解析器会直接报 Parse error。
立即学习“PHP免费学习笔记(深入)”;
- 这意味着:含这些语法的文件在 8.3 下根本无法加载,连 autoloader 都进不去
- 无法用条件判断绕过:
if (PHP_VERSION_ID >= 80400)对解析阶段无效 - 若你用 Composer 包依赖了 8.4 特性(如某新版 DTO 库),整个项目必须统一升到 8.4
- 反向兼容建议:新项目可直接用,老项目迁移时优先替换为传统 getter/setter + 类型断言
类型化常量和 #[Override] 在 8.3 已是硬门槛
别误以为这是 8.4 的问题——const int CODE = 200; 和 #[\Override] 在 8.3 就已强制校验,但很多团队拖到 8.4 升级时才集中暴露。
-
const无类型声明 → 8.3 报Fatal error: Cannot use array as value for class constant -
#[\Override]标在不存在/拼错名/属于 trait 的方法上 → 类加载即失败,不是运行时报错 - 静态分析工具如 PHPStan level 8+ 能提前扫出这些,但很多项目没开这个级别
- 真实风险:CI 流水线在 8.3 下通过,一上 8.4 就挂,其实是 8.3 的兼容债拖到了 8.4 才清算
最易被忽略的是:8.4 的 readonly class 和 8.3 的 readonly 属性不是渐进关系,而是两种不可混用的模型。前者锁整个对象生命周期,后者只管单次赋值;试图把一个 8.3 的只读属性类直接加 readonly 关键字,大概率因构造逻辑冲突而失败。



















