标题提到的PHP 8.2,在本文写作时(2022年9月)尚未正式发布,正式版于2022年12月发布。我们团队为了提前适配,在测试环境用了PHP 8.2 RC版本,结果遇到了一个因为新特性导致的Bug。
虽然是在测试环境,但问题很典型,排查过程也很有参考价值。本文分享这次排查的完整过程,包括问题现象、排查思路、根本原因、解决方案,以及升级PHP版本的注意事项。
一、背景
1. 为什么提前用PHP 8.2
PHP 8.2带来了很多新特性和性能改进:
- 只读类(Readonly Classes)
- 弃用动态属性(Dynamic Properties Deprecated)
- 新的类型系统改进(独立类型、交集类型改进等)
- 性能优化
- 新的函数和类
我们团队一直有跟进新版本的习惯,所以在PHP 8.2 RC版本出来后,就在测试环境部署了,想提前发现兼容性问题,等正式版发布后能顺利升级。
2. 项目情况
我们的项目是一个中型的Web应用:
- 框架:Laravel 9
- PHP版本:生产环境PHP 8.1,测试环境PHP 8.2 RC
- 代码量:约20万行
- 依赖:大量Composer包
- 部署:Docker容器
二、问题现象
1. 测试环境报错
部署到PHP 8.2测试环境后,一开始一切正常。但跑了几天,某些页面开始报错。
错误信息是:
Deprecated: Creation of dynamic property ... is deprecated这是一个弃用警告(Deprecated),不是致命错误。但我们的测试环境把所有错误都显示出来了,所以页面上满是警告。
更严重的是,某些接口返回的数据不对,有些字段丢失了。
2. 问题特点
这个问题有几个特点:
- 只在PHP 8.2环境出现,PHP 8.1正常
- 不是所有页面都报错,只有某些特定的页面
- 报错信息都是关于动态属性的
- 有些功能虽然不报错,但数据不对
三、排查过程
1. 初步分析
看到错误信息,我第一反应是:PHP 8.2弃用了动态属性。
在PHP 8.2之前,你可以给对象动态添加属性,不需要在类中声明:
class User {
public $name;
}
$user = new User();
$user->age = 25; // 动态属性,PHP 8.1及之前可以,PHP 8.2弃用PHP 8.2开始,动态属性被弃用,会抛出Deprecated警告。未来的PHP版本会移除这个特性。
但我们的代码里,应该没有动态属性啊?都是用的框架,属性都在类里声明了。
2. 定位报错的代码
我根据报错信息,定位到了具体的代码。
报错的地方,是一个模型类:
class Product extends Model {
// 没有声明属性,用的是Laravel的魔术方法
}Laravel的Eloquent模型,用的是魔术方法get和set来访问属性。属性存在$attributes数组里,不是真正的对象属性。
但问题出在,我们有些代码,给模型对象动态添加了自定义属性:
$product = Product::find(1);
$product->custom_field = 'some value'; // 动态属性!这些代码在PHP 8.1中是正常的,但在PHP 8.2中就被弃用了。
3. 更严重的问题
动态属性的弃用警告,只是表面问题。更严重的是,有些数据丢失了。
我仔细检查后发现,问题出在JSON序列化上。
我们的API接口,返回的是模型的JSON。在PHP 8.1中,动态属性会被包含在JSON中。但在PHP 8.2中,动态属性的处理方式变了,有些动态属性没有被正确序列化。
具体来说,Laravel的Arrayable和Jsonable接口,在处理动态属性时,和PHP 8.2的新行为有冲突。
4. 深入分析
我深入研究了PHP 8.2的变化:
变化一:动态属性弃用
- PHP 8.2中,给未声明的属性赋值,会抛出Deprecated警告
- 但如果类使用了
#[AllowDynamicProperties]注解,就允许动态属性 - stdClass和使用了
get/set魔术方法的类,不受影响
变化二:只读类
- PHP 8.2引入了只读类,用
readonly关键字声明 - 只读类的所有属性都是只读的,只能在构造函数中赋值
- 只读类不能有动态属性
变化三:类型系统改进
- PHP 8.2改进了类型系统
- 某些类型的处理方式变了
- 空值和类型转换的行为有细微变化
5. 找到根本原因
经过仔细排查,我找到了根本原因:
我们的代码中,有一个基类,用了动态属性来存储扩展数据。这个基类没有声明这些属性,也没有用#[AllowDynamicProperties]注解。
在PHP 8.1中,这没问题。但在PHP 8.2中:
- 动态属性被弃用,抛出警告
- 某些情况下,动态属性没有被正确初始化
- JSON序列化时,这些属性丢失了
更麻烦的是,我们依赖的一些第三方包,也用了动态属性。这些包还没有适配PHP 8.2。
四、解决方案
找到问题后,开始解决。
1. 紧急修复
首先,紧急修复测试环境的问题:
方案一:给使用动态属性的类加上#[AllowDynamicProperties]注解
#[AllowDynamicProperties]
class BaseModel {
// ...
}方案二:在类中声明所有属性,不用动态属性
class BaseModel {
public $custom_field;
// ...
}方案三:用魔术方法get/set,把属性存在数组里
class BaseModel {
private $data = [];
public function __get($key) {
return $this->data[$key] ?? null;
}
public function __set($key, $value) {
$this->data[$key] = $value;
}
}我们选择了方案一(加注解)作为紧急修复,因为改动最小,能快速解决问题。
2. 根本修复
然后,做根本修复:
- 审查所有代码,找出使用动态属性的地方
- 能声明属性的,都声明属性
- 必须用动态属性的,加上
#[AllowDynamicProperties]注解 - 升级依赖的第三方包,到支持PHP 8.2的版本
- 对不支持PHP 8.2的包,找替代方案或临时补丁
3. 增加检查
在CI/CD流程中增加PHP 8.2的兼容性检查:
- 用PHP 8.2运行单元测试
- 用静态分析工具(如PHPStan)检查动态属性
- 代码审查时,注意动态属性的使用
五、PHP 8.2其他需要注意的新特性
除了动态属性,PHP 8.2还有一些新特性,升级时需要注意:
1. 只读类(Readonly Classes)
PHP 8.2引入了只读类:
readonly class User {
public string $name;
public int $age;
public function __construct(string $name, int $age) {
$this->name = $name;
$this->age = $age;
}
}只读类的所有属性都是只读的,只能在构造函数中赋值。这对于值对象(Value Object)很有用。
注意:
- 只读类不能有动态属性
- 只读类的属性不能有默认值(除了构造函数中)
- 只读类不能被继承(除非子类也是只读的)
2. 弃用的功能
PHP 8.2弃用了一些功能:
- 动态属性(前面说的)
- 某些
utf8encode和utf8decode的用法 - 某些字符串的隐式转换
${}字符串插值语法
升级时,要检查代码中是否用了这些被弃用的功能。
3. 新的类型和函数
PHP 8.2增加了一些新的类型和函数:
true和false作为独立类型null和false的交集类型- 新的字符串函数和数组函数
mysqli的一些改进
这些新特性,可以在升级后逐步使用。
4. 性能改进
PHP 8.2有一些性能改进:
- 某些操作的速度提升
- 内存使用优化
- JIT的改进
但实际性能提升,取决于具体的应用。建议升级后做性能测试。
六、升级PHP版本的一般步骤
总结一下升级PHP版本的一般步骤:
1. 了解新版本的变化
- 阅读官方的升级指南和迁移文档
- 了解新特性、弃用的功能、不兼容的变化
- 关注性能变化和安全改进
2. 在测试环境部署
- 先在测试环境部署新版本
- 不要直接在生产环境升级
- 用Docker等容器技术,方便切换版本
3. 运行测试
- 运行单元测试、集成测试、功能测试
- 测试所有核心功能
- 关注错误日志和警告
4. 修复兼容性问题
- 修复弃用警告
- 修复不兼容的代码
- 升级依赖的第三方包
- 对不兼容的包,找替代方案
5. 性能测试
- 对比新旧版本的性能
- 关注响应时间、内存使用、CPU使用
- 优化性能瓶颈
6. 灰度发布
- 不要一次性全量升级
- 先灰度发布到部分服务器
- 监控错误率和性能指标
- 确认稳定后,再全量升级
7. 回滚方案
- 准备好回滚方案
- 如果出问题,能快速回滚到旧版本
- 保留旧版本的环境和配置
七、踩过的坑
坑一:第三方包不兼容
升级PHP版本,最大的问题往往不是自己的代码,而是第三方包。
很多第三方包更新慢,不支持新版本的PHP。我们项目中,有好几个包不支持PHP 8.2,有的甚至已经停止维护了。
解决:
- 提前检查依赖包的兼容性
- 对不支持的包,找替代方案
- 或者给包提交PR,帮助适配新版本
- 实在不行,临时用补丁
教训: 升级前,先检查所有依赖包的兼容性。
坑二:弃用警告被忽略
PHP的弃用警告(Deprecated),很多人会忽略,觉得不是错误。
但弃用警告意味着,未来的版本会移除这个功能。如果现在不处理,将来升级到下一个版本,就会变成致命错误。
解决:
- 把弃用警告当成错误来处理
- 在开发环境显示所有错误
- 在CI中检查弃用警告
- 及时修复,不要拖延
教训: 不要忽略弃用警告,现在不修,将来更麻烦。
坑三:魔术方法的行为变化
PHP新版本中,魔术方法(get、set、__call等)的行为有时候会有细微变化。
我们的代码中,有一个用了__get的类,在PHP 8.2中行为变了,导致某些属性访问不到。
解决:
- 仔细阅读新版本的魔术方法文档
- 测试所有用了魔术方法的类
- 不要过度依赖魔术方法的边缘行为
教训: 魔术方法是黑魔法,能不用就不用。用了就要仔细测试。
坑四:测试覆盖不够
我们的单元测试覆盖率不高,很多边缘情况没有测试到。升级后,一些不常用的功能出了问题,测试没有发现。
解决:
- 提高测试覆盖率
- 重点测试核心功能和边缘情况
- 升级后,做全面的回归测试
教训: 测试是升级的安全网,测试覆盖越高,升级越放心。
八、写在最后
这次PHP 8.2的兼容性问题,虽然是在测试环境发现的,但给我们敲响了警钟。
PHP的版本更新越来越快,新特性越来越多,弃用的功能也越来越多。作为开发者,我们要跟进新版本,但也要注意兼容性问题。
升级PHP版本,不是一件小事,需要认真对待。提前了解变化,在测试环境充分验证,修复兼容性问题,灰度发布,准备回滚方案。每一步都不能少。
2022年了,PHP 8.2即将发布。它带来了很多好的特性,也移除了一些旧的写法。对于我们PHP开发者来说,这既是挑战,也是机遇。拥抱变化,持续学习,才能跟上技术的步伐。
最后,用一句话总结:"PHP升级,提前了解变化,充分测试,逐步迁移。不要忽略弃用警告,不要直接在生产环境升级。"
愿你的PHP升级,一帆风顺,不用熬夜排查问题。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录