Composer本身不参与只读属性校验,但加载的第三方库若用ReflectionProperty::setValue()修改readonly属性,PHP运行时会立即抛出Error: Cannot modify readonly property,该Error无法被try/catch Exception捕获,必须用catch (Error $e)处理。

Composer 库本身不直接参与 PHP 的只读属性(readonly)校验,但当你用 composer install 加载的第三方库(比如 Doctrine、Symfony 或自定义 DTO 工具)尝试通过反射修改只读属性时,会立即失败——不是 Composer 限制你,而是 PHP 运行时在执行反射赋值时抛出 Error。
为什么 ReflectionProperty::setValue() 在只读属性上直接报错
PHP 8.2+ 对只读属性的保护是运行时强制的,哪怕绕过构造函数、用反射强行写入,也会触发底层 Zend VM 的访问控制检查。
-
ReflectionProperty::setValue()在尝试写入readonly属性时,不管是否已初始化,都会抛出Error: Cannot modify readonly property - 这个错误无法被
try/catch捕获为Exception,因为它是Error类型,必须用catch (Error $e) - 即使属性是
public且未被private封装,只要带readonly修饰符,反射写入就无效
常见依赖注入器(如 Symfony DI、PHP-DI)在只读类下的行为差异
主流 DI 容器默认使用反射设置属性,遇到只读类或只读属性时表现不一:
- Symfony 6.4+ 的
ContainerBuilder默认跳过只读属性,不尝试赋值,但也不会报错——它靠ReflectionProperty::isReadOnly()提前判断 - PHP-DI 7.x 仍尝试反射赋值,碰到
readonly属性会直接崩溃,除非你显式配置setDefinition()并禁用自动属性注入 - 如果你用
new ClassName(...)手动构造只读类,DI 容器通常不会碰它的属性;但若用了@Inject或#[Autowire]注解,且目标是只读属性,则必须改用构造函数参数注入
如何让 Composer 加载的库兼容只读属性
关键不是改 Composer,而是调整你或你依赖的库的初始化方式:
立即学习“PHP免费学习笔记(深入)”;
- 避免依赖“反射设值”模式:把所有只读字段移到构造函数参数中,用
__construct(public readonly string $id)提升语法 - 检查你使用的序列化/ORM 库是否调用
ReflectionProperty::setValue()—— Doctrine 3.0+ 已适配isReadOnly(),但老版本需手动补丁 - 若必须动态构建只读对象(如测试场景),改用
unserialize()+ 自定义__unserialize(),而非反射赋值 - 不要在
composer.json中降级 PHP 版本规避问题——PHP 8.2 的只读语义是硬性约束,绕不开
最常被忽略的一点:只读属性的不可变性在反序列化时也生效,但 unserialize() 本身不触发构造函数,所以只读属性若未在 __unserialize() 中显式恢复,就会保持未初始化状态,导致后续访问抛出 Uninitialized readonly property 错误。



















