PHP 8.3 禁用动态属性,需根据场景选择补声明属性、加#[\AllowDynamicProperties]注解或实现__set();stdClass天然豁免,但继承后仍需注解;PHP 9.0 将彻底移除该特性。

直接加 #[\AllowDynamicProperties] 注解不是万能解法,得先判断这个警告是不是真该被允许——很多情况下,它只是拼写错误或结构混乱的信号。
为什么会出现 Creation of dynamic property is deprecated 警告
PHP 8.3 默认禁止向未声明属性的类实例写入新字段,比如 $user->email = 'a@b.c',而 User 类里根本没定义 $email。这不是 bug,是语言强制你“说清楚”对象结构。
- 触发条件:类没声明该属性 + 没定义
__set()+ 没加#[\AllowDynamicProperties] - 常见误判场景:JSON 解码后直接转成普通对象(
json_decode($json)返回stdClass,它本身被豁免;但如果你json_decode($json, false)后又 cast 成自定义类,就踩坑了) - 注意:
stdClass和它的子类天然豁免,不需要注解;但你继承stdClass再加自己的逻辑,仍需显式标注才能保留动态行为
三种处理方式,选哪个取决于你的真实需求
别一上来就加注解。先看代码意图:
- 如果是 DTO、配置类、ORM 实体这类“结构明确”的类:直接补声明属性,比如
public string|null $email;—— 这是最安全、IDE 可提示、静态分析能覆盖的方案 - 如果确实是运行时任意字段(如表单收集器、通用响应包装器):用
#[\AllowDynamicProperties],但必须写全命名空间,#[AllowDynamicProperties](缺反斜杠)会报Fatal error: Undefined attribute - 如果想彻底禁用且升级为错误(比如 CI 环境强校验):在类里定义
__set()并抛出InvalidArgumentException,同时确保zend.assertions = 1且assert.exception = 1生效
容易踩的坑和兼容性细节
这些错误不会立刻报错,但上线后可能突然失效:
立即学习“PHP免费学习笔记(深入)”;
-
#[\AllowDynamicProperties]必须放在class关键字**正前方**,不能换行、不能缩进、不能跟其他注解挤在同一行不加分隔符 - 父类加了注解,子类自动继承;但如果子类自己也写了注解,必须保持一致命名空间,否则解析失败
- 用了
__set()就绕过整个弃用机制——也就是说,即使你不加注解,也不会警告;但这不是推荐解法,因为掩盖了设计问题 - PHP 9.0 计划彻底移除动态属性支持,届时
#[\AllowDynamicProperties]也会失效,所有动态赋值将变成致命错误
最麻烦的不是加注解,而是那些散落在各处的 $obj->xxx 赋值——它们可能跨多个文件、被魔术方法拦截、或藏在第三方库回调里。建议用 PHPStan 或 Psalm 扫描未声明属性访问,比手动 grep 更可靠。



















