Skip to content

版本迁移指南

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: 类型不匹配产生 TypeError

PHP 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 环境验证。 ==="

注意事项

升级安全建议

  1. 不要在生产环境直接升级,先在 staging 环境充分测试
  2. 升级前创建 git tag,确保可以快速回退
  3. 逐版本升级(7.4 → 8.0 → 8.1 → 8.2),不要跨版本
  4. 检查所有第三方库的兼容性
  5. 关注 PHP 扩展的兼容性(特别是 PECL 扩展)

最佳实践

  1. 定期升级:不要等到旧版本安全支持结束后再升级
  2. 使用兼容性检查工具:在 CI 中集成 PHPCompatibility
  3. 渐进式升级:先升级开发环境,再升级 staging,最后生产
  4. 测试覆盖:确保足够的测试覆盖率(至少 80%)再升级
  5. 关注 RFC:提前了解下个版本的变更

下一节

继续学习:弃用与移除功能清单

参考链接