版本迁移指南
PHP 版本迁移是一个需要仔细规划的过程。每个新版本都可能引入不兼容的变更(Breaking Changes)和废弃功能(Deprecated Features)。本节提供从 PHP 7.4 到 8.4 的升级检查清单、兼容性处理方法、废弃功能替代方案以及迁移工具推荐。
升级检查清单
升级前准备
bash
# 1. 检查当前 PHP 版本
php -v
# 2. 检查项目中使用的 PHP 特性
composer require --dev phpstan/phpstan
vendor/bin/phpstan analyse src/ --level=5
# 3. 运行完整测试套件
vendor/bin/phpunit --testdox
# 4. 检查 Composer 依赖兼容性
composer outdated --direct
# 5. 检查 PHP 配置
php -i | grep "php.ini"升级步骤
bash
# 1. 备份
# - 备份代码(git tag)
# - 备份数据库
# - 备份 php.ini 和 php-fpm 配置
# 2. 更新 Composer 依赖的 PHP 版本要求
composer require php:"^8.2" --no-update
# 3. 在本地安装新版本 PHP
# macOS: brew install php@8.2
# Ubuntu: sudo add-apt-repository ppa:ondrej/php && sudo apt install php8.2
# 4. 更新依赖
composer update
# 5. 运行测试
vendor/bin/phpunit
# 6. 运行代码质量检查
vendor/bin/phpstan analyse src/ --level=8
# 7. 部署到 staging 环境验证
# 8. 部署到生产环境版本兼容性矩阵
text
升级路径:
PHP 7.4 → 8.0 : 中等风险,多处不兼容变更
PHP 8.0 → 8.1 : 低风险,主要为新增特性
PHP 8.1 → 8.2 : 低风险,少量废弃
PHP 8.2 → 8.3 : 极低风险,主要为新增特性
PHP 8.3 → 8.4 : 中等风险,语法变更较多
建议:可以直接从 PHP 7.4 升级到 PHP 8.1+(一次性处理所有不兼容变更)兼容性处理
PHP 7.4 → 8.0 主要变更
php
<?php
declare(strict_types=1);
// 1. 字符串与数字比较变更
// PHP 7.x: 0 == "foo" → true
// PHP 8.0: 0 == "foo" → false
// 修复:使用严格比较
if ($value === 0) { } // 而非 $value == 0
// 2. 移除了 create_function()(建议使用闭包)
// PHP 7.x:
$fn = create_function('$x', 'return $x * 2;');
// PHP 8.0:
$fn = fn ($x) => $x * 2;
// 3. $GLOBALS 的行为变更
// PHP 7.x: 修改 $GLOBALS['var'] 影响全局变量
// PHP 8.0: $GLOBALS 不再支持间接引用
// 修复:直接使用 global 关键字
// 4. 数字字符串的处理变更
// PHP 7.x: "1" + "2" = 3(隐式类型转换)
// PHP 8.0: 行为更严格
$sum = (int) "1" + (int) "2"; // 显式转换更安全
// 5. Reflection 类型变更
// Reflection::export() 被移除
// 替代:使用 ReflectionClass::__toString()
// 6. 移除了 money_format()
// 替代:使用 NumberFormatter
$formatter = new NumberFormatter('zh_CN', NumberFormatter::CURRENCY);
echo $formatter->formatCurrency(99.99, 'CNY');
// 7. get_magic_quotes_gpc() 被移除
// 该函数在 PHP 5.4 已废弃,8.0 正式移除
// 8. implode() 参数顺序变更
// PHP 7.4: implode($glue, $array) 和 implode($array) 都支持
// PHP 8.0: implode($array) 被移除,只支持 implode($separator, $array)
// 9. 参数类型不匹配现在是 TypeError
// PHP 7.x: 类型不匹配产生 Warning
// PHP 8.0: 类型不匹配产生 TypeErrorPHP 8.0 → 8.1 主要变更
php
<?php
declare(strict_types=1);
// 1. 返回类型声明不匹配现在是 TypeError
// PHP 8.0: 内部函数返回类型不匹配产生 Warning
// PHP 8.1: 产生 TypeError
// 2. $GLOBALS 的进一步限制
// 不再支持通过引用修改 $GLOBALS 中的值
// 3. Serializable 接口废弃
// 替代:使用 __serialize() 和 __unserialize()
// 4. finfo_file() 的 MIME 类型变更
// 某些文件的 MIME 类型描述有变化PHP 8.1 → 8.2 主要变更
php
<?php
declare(strict_types=1);
// 1. 部分动态属性被弃用(PHP 9.0 将移除)
class User
{
public string $name;
public function setDynamicProperty(): void
{
// PHP 8.2: Deprecated: Creation of dynamic property User::$foo
$this->foo = 'bar';
}
}
// 修复:声明属性
class UserFixed
{
public string $name;
public string $foo = ''; // 显式声明
public function setDynamicProperty(): void
{
$this->foo = 'bar'; // OK
}
}
// 允许使用 #[AllowDynamicProperties]
#[\AllowDynamicProperties]
class UserDynamic
{
// 允许动态属性
}PHP 8.2 → 8.3 主要变更
php
<?php
declare(strict_types=1);
// 1. get_class() / get_class($this) 在未绑定闭包中不再返回 null
// PHP 8.2: 返回 null
// PHP 8.3: 抛出 Error
// 2. 方法调用中 INI 解析的变更
// date.timezone 必须设置
// 3. 冻结的 DateTime 对象不能被修改
$dt = new DateTimeImmutable('2024-01-01');
// $dt->modify('+1 day'); // ❌ DateTimeImmutable 返回新对象
$newDt = $dt->modify('+1 day'); // ✅PHP 8.3 → 8.4 主要变更
php
<?php
declare(strict_types=1);
// 1. 隐式可空类型参数被弃用
function foo(int|null $x = null) {} // ❌ Deprecated
function foo(?int $x = null) {} // ✅ 推荐写法
// 2. 类构造器的 new ClassName(args) 推荐使用命名参数
// PHP 8.4 的属性钩子改变了许多传统模式
// 3. CLI 中 multi-byte 字符的变更
// mb_strtolower(), mb_strtoupper() 默认行为调整迁移工具
PHPCompatibility
bash
# 安装 PHPCompatibility 编码标准
composer require --dev phpcompatibility/php-compatibility
# 配置 phpcs.xml
# <config name="installed_paths" value="vendor/phpcompatibility/php-compatibility"/>
# <rule ref="PHPCompatibility"/>
# 检查代码与 PHP 8.2 的兼容性
vendor/bin/phpcs --standard=PHPCompatibility --runtime-set testVersion 8.2 src/
# 检查与 PHP 8.1-8.3 的兼容性
vendor/bin/phpcs --standard=PHPCompatibility --runtime-set testVersion 8.1- src/Rector(自动代码升级)
bash
# 安装 Rector
composer require --dev rector/rector
# 创建 rector.php 配置
# 然后运行自动升级
vendor/bin/rector process src/PHPStan 跨版本检查
bash
# 使用 PHPStan 检查不兼容问题
vendor/bin/phpstan analyse src/ --level=8 --php-version=8.2实战示例
从 PHP 7.4 升级到 PHP 8.2 的完整流程
bash
#!/bin/bash
# upgrade-php.sh
set -euo pipefail
echo "=== PHP 7.4 → 8.2 升级流程 ==="
# 1. 创建升级分支
git checkout -b upgrade/php-8.2
# 2. 运行兼容性检查
echo "Step 1: 检查兼容性..."
vendor/bin/phpcs --standard=PHPCompatibility --runtime-set testVersion 8.2 src/
# 3. 更新 composer.json PHP 版本要求
echo "Step 2: 更新 composer.json..."
composer require php:"^8.2" --no-update --no-interaction
# 4. 更新依赖
echo "Step 3: 更新依赖..."
composer update --with-all-dependencies --no-interaction
# 5. 修复兼容性问题
echo "Step 4: 修复兼容性问题..."
vendor/bin/rector process src/
# 6. 运行代码风格修复
echo "Step 5: 代码风格修复..."
vendor/bin/php-cs-fixer fix
# 7. 运行静态分析
echo "Step 6: 静态分析..."
vendor/bin/phpstan analyse src/ --level=8
# 8. 运行测试
echo "Step 7: 运行测试..."
vendor/bin/phpunit --testdox
# 9. 提交
echo "Step 8: 提交变更..."
git add -A
git commit -m "Upgrade PHP to 8.2"
echo "=== 升级完成!请部署到 staging 环境验证。 ==="注意事项
升级安全建议
- 不要在生产环境直接升级,先在 staging 环境充分测试
- 升级前创建 git tag,确保可以快速回退
- 逐版本升级(7.4 → 8.0 → 8.1 → 8.2),不要跨版本
- 检查所有第三方库的兼容性
- 关注 PHP 扩展的兼容性(特别是 PECL 扩展)
最佳实践
- 定期升级:不要等到旧版本安全支持结束后再升级
- 使用兼容性检查工具:在 CI 中集成 PHPCompatibility
- 渐进式升级:先升级开发环境,再升级 staging,最后生产
- 测试覆盖:确保足够的测试覆盖率(至少 80%)再升级
- 关注 RFC:提前了解下个版本的变更
下一节
继续学习:弃用与移除功能清单