简介

PHP 8.2 引入了许多类型系统改进,包括只读类(Readonly Classes)、析取范式类型(DNF Types)、独立的 nulltruefalse 类型、敏感参数隐藏、新的 Random 扩展等。本文将介绍主要变化和如何从 PHP 8.1 迁移到 PHP 8.2。

参考资源

PHP 8.2 新特性

只读类(Readonly Classes)

PHP 8.2 允许将整个类声明为只读,类中的所有属性自动成为只读属性:

readonly class User {
    public function __construct(
        public string $username,
        public string $email,
    ) {}
}

$user = new User('john_doe', 'john@example.com');
echo $user->username; // john_doe

$user->email = 'new@example.com';
// Fatal error: Cannot modify readonly property User::$email

只读类的特性:

  • 所有属性自动成为只读
  • 不能有动态属性
  • 所有属性必须有类型声明
  • 子类也必须是只读的

析取范式类型(DNF Types)

析取范式(Disjunctive Normal Form)允许组合联合类型和交叉类型,交叉类型必须用括号分组:

// 接受同时实现 Countable 和 Iterator 的对象,或者 null
function process((Countable&Iterator)|null $item): int {
    return $item ? count($item) : 0;
}

// 更复杂的 DNF 类型示例
function handle(
    (HTMLRequest&RequestInterface)|APIRequest|null $request
): Response {
    // 处理请求
}

DNF 类型必须遵循特定格式:交叉类型用括号包裹,再与其他类型用 | 连接。

独立的 null、true、false 类型

PHP 8.2 允许将 nulltruefalse 作为独立的类型使用:

// 函数总是返回 false
function alwaysFalse(): false {
    return false;
}

// 函数总是返回 true
function alwaysTrue(): true {
    return true;
}

// 函数总是返回 null
function alwaysNull(): null {
    return null;
}

// 实际应用示例:失败时返回 false
function findUser(int $id): User|false {
    $user = // 查询数据库...
    return $user ?? false;
}

这比使用 bool?type 提供了更精确的类型信息。

Trait 中的常量

PHP 8.2 允许在 Trait 中定义常量:

trait NetworkTrait {
    public const TIMEOUT = 60;
    private const MAX_RETRIES = 3;

    public function getTimeout(): int {
        return self::TIMEOUT;
    }
}

class HttpClient {
    use NetworkTrait;

    public function connect(): void {
        echo "Timeout: " . self::TIMEOUT; // 60
    }
}

// 通过使用 Trait 的类访问常量
echo HttpClient::TIMEOUT; // 60

敏感参数隐藏(#[\SensitiveParameter])

新的 #[\SensitiveParameter] 属性可以在堆栈跟踪中隐藏敏感数据:

function hashPassword(
    #[\SensitiveParameter] string $password
): string {
    debug_print_backtrace();
    return password_hash($password, PASSWORD_DEFAULT);
}

hashPassword('my_secret_password');
// PHP 8.1: #0 example.php(8): hashPassword('my_secret_password')
// PHP 8.2: #0 example.php(8): hashPassword(Object(SensitiveParameterValue))

这对于防止密码、API 密钥等敏感信息在错误日志中泄露非常有用。

新的 Random 扩展

PHP 8.2 重构了随机数生成,提供了面向对象的 API:

// 使用默认引擎
$randomizer = new \Random\Randomizer();

// 生成随机整数
echo $randomizer->getInt(1, 100);

// 生成随机字节
echo bin2hex($randomizer->getBytes(16));

// 随机打乱数组
$array = [1, 2, 3, 4, 5];
$shuffled = $randomizer->shuffleArray($array);

// 随机打乱字符串
$string = "Hello";
echo $randomizer->shuffleBytes($string);

// 使用特定引擎
$secure = new \Random\Randomizer(new \Random\Engine\Secure());
$mt = new \Random\Randomizer(new \Random\Engine\Mt19937(seed: 1234));

可用的引擎:

  • \Random\Engine\Secure - 加密安全的随机数
  • \Random\Engine\Mt19937 - Mersenne Twister 算法
  • \Random\Engine\PcgOneseq128XslRr64 - PCG 算法
  • \Random\Engine\Xoshiro256StarStar - Xoshiro256** 算法

#[\AllowDynamicProperties] 属性

由于动态属性已被弃用,如果仍需使用,可以用此属性标记类:

#[\AllowDynamicProperties]
class LegacyClass {
    public string $declared = 'value';
}

$obj = new LegacyClass();
$obj->undeclared = 'dynamic'; // 不会产生弃用警告

枚举中获取常量

PHP 8.2 允许在枚举表达式中获取常量:

enum Status: string {
    case Active = 'active';
    case Inactive = 'inactive';

    const DEFAULT = self::Active;
}

echo Status::DEFAULT->value; // active

PHP 8.2 新增函数

ini_parse_quantity()

解析 PHP INI 格式的数据大小值:

// 解析 INI 格式的容量值
echo ini_parse_quantity('256M');  // 268435456 (256 * 1024 * 1024)
echo ini_parse_quantity('1G');    // 1073741824
echo ini_parse_quantity('512K');  // 524288

memory_reset_peak_usage()

重置峰值内存使用量跟踪:

// 初始峰值
echo memory_get_peak_usage(); // 例如: 2097152

// 进行一些内存密集操作
$data = str_repeat('x', 1000000);
echo memory_get_peak_usage(); // 例如: 3145728

// 重置峰值
memory_reset_peak_usage();
unset($data);

echo memory_get_peak_usage(); // 重置后的新峰值

mysqli_execute_query()

简化 MySQLi 预处理语句的执行:

// PHP 8.2 之前
$stmt = $mysqli->prepare('SELECT * FROM users WHERE id = ?');
$stmt->bind_param('i', $id);
$stmt->execute();
$result = $stmt->get_result();

// PHP 8.2 - 使用 mysqli_execute_query()
$result = mysqli_execute_query(
    $mysqli,
    'SELECT * FROM users WHERE id = ?',
    [$id]
);

// 面向对象写法
$result = $mysqli->execute_query(
    'SELECT * FROM users WHERE id = ?',
    [$id]
);

curl_upkeep()

保持持久 HTTP 连接活跃:

$ch = curl_init('https://example.com');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_exec($ch);

// 保持连接活跃
curl_upkeep($ch);

openssl_cipher_key_length()

获取 OpenSSL 加密算法所需的密钥长度:

echo openssl_cipher_key_length('aes-256-cbc'); // 32
echo openssl_cipher_key_length('aes-128-gcm'); // 16
echo openssl_cipher_key_length('des-ede3-cbc'); // 24

sodium_crypto_stream_xchacha20_xor_ic()

XChaCha20 加密函数,支持初始计数器:

$key = sodium_crypto_stream_xchacha20_keygen();
$nonce = random_bytes(SODIUM_CRYPTO_STREAM_XCHACHA20_NONCEBYTES);
$message = 'Hello, World!';

// 使用初始计数器加密
$encrypted = sodium_crypto_stream_xchacha20_xor_ic(
    $message,
    $nonce,
    1,  // 初始计数器
    $key
);

弃用特性

动态属性弃用

在未声明的类属性上进行读写操作已被弃用:

class User {
    public string $name;
}

$user = new User();
$user->name = 'John';        // ✅ 正常
$user->age = 25;             // ⚠️ Deprecated: Creation of dynamic property

// 解决方案 1:声明属性
class User {
    public string $name;
    public int $age;
}

// 解决方案 2:使用 #[AllowDynamicProperties]
#[\AllowDynamicProperties]
class User {
    public string $name;
}

// 解决方案 3:使用 __get/__set 魔术方法
class User {
    private array $data = [];

    public function __get(string $name): mixed {
        return $this->data[$name] ?? null;
    }

    public function __set(string $name, mixed $value): void {
        $this->data[$name] = $value;
    }
}

stdClass 和从 __get()/__set() 继承的类不受影响。

${var} 字符串插值弃用

旧的 ${var} 字符串插值语法已被弃用:

$name = 'PHP';

// ❌ 已弃用
echo "Hello ${name}";
// Deprecated: Using ${var} in strings is deprecated, use {$var} instead

// ❌ 变量变量也已弃用
$var = 'name';
echo "Hello ${$var}";
// Deprecated: Using ${expr} in strings is deprecated, use {${expr}} instead

// ✅ 推荐写法
echo "Hello {$name}";
echo "Hello {$$var}";

utf8_encode() 和 utf8_decode() 弃用

这两个函数已被弃用,因为它们只支持 Latin-1 编码:

// ❌ 已弃用
$encoded = utf8_encode($string);
$decoded = utf8_decode($string);

// ✅ 使用 mbstring
$encoded = mb_convert_encoding($string, 'UTF-8', 'ISO-8859-1');
$decoded = mb_convert_encoding($string, 'ISO-8859-1', 'UTF-8');

// ✅ 或使用 iconv
$encoded = iconv('ISO-8859-1', 'UTF-8', $string);
$decoded = iconv('UTF-8', 'ISO-8859-1', $string);

部分可调用模式弃用

一些可调用模式已被弃用:

// ❌ 已弃用的可调用模式
"self::method"
"parent::method"
"static::method"
["self", "method"]
["parent", "method"]
["static", "method"]
["Foo", "Bar::method"]
[new Foo, "Bar::method"]

// ✅ 使用一等可调用语法
$callable = $this->method(...);
$callable = self::method(...);
$callable = Foo::method(...);

Mbstring 编码弃用

以下 Mbstring 编码已被弃用:

  • BASE64
  • UUENCODE
  • HTML-ENTITIES
  • Quoted-Printable
// ❌ 已弃用
$encoded = mb_convert_encoding($data, 'BASE64');

// ✅ 使用专门的函数
$encoded = base64_encode($data);
$decoded = base64_decode($encoded);

// ✅ HTML 实体使用
$encoded = htmlentities($string, ENT_QUOTES, 'UTF-8');
$decoded = html_entity_decode($encoded, ENT_QUOTES, 'UTF-8');

兼容性变化

strtolower() 和 strtoupper() 不再受区域设置影响

setlocale(LC_ALL, 'tr_TR');

// PHP 8.1: 受区域设置影响
// PHP 8.2: 始终按 ASCII 规则处理
echo strtolower('I'); // 'i'(不再是土耳其语的 'ı')
echo strtoupper('i'); // 'I'(不再是土耳其语的 'İ')

// 需要区域敏感转换时使用 mbstring
echo mb_strtolower('I', 'tr_TR'); // 'ı'

str_split() 空字符串行为变化

// PHP 8.1: 返回 ['']
// PHP 8.2: 返回 []
$result = str_split('');
var_dump($result); // array(0) {}

ksort() SORT_REGULAR 行为变化

$arr = ['b' => 1, 'a' => 2, 10 => 3, 2 => 4];
ksort($arr, SORT_REGULAR);

// PHP 8.2 中排序更加一致
// 数字键和字符串键的比较规则更加明确

日期时间扩展变化

// DateTimeImmutable 和 DateTime 的行为更加一致
// DatePeriod 现在是可迭代的

$period = new DatePeriod(
    new DateTime('2024-01-01'),
    new DateInterval('P1D'),
    5
);

// 可以使用 foreach 遍历
foreach ($period as $date) {
    echo $date->format('Y-m-d');
}

其他改进

ReflectionMethod 和 ReflectionProperty 改进

// 检查方法是否可以作为静态方法调用
$reflection = new ReflectionMethod(MyClass::class, 'myMethod');
var_dump($reflection->isStatic());

// 检查属性是否是只读
$reflection = new ReflectionProperty(User::class, 'name');
var_dump($reflection->isReadOnly());

ZipArchive 新方法

$zip = new ZipArchive();
$zip->open('archive.zip', ZipArchive::CREATE);

// 获取文件流
$stream = $zip->getStreamIndex(0);
$stream = $zip->getStreamName('file.txt');

// 清除错误状态
$zip->clearError();

ODBC 和 PDO_ODBC 改进

连接字符串和凭证检查得到改进,支持更多的 ODBC 功能。

总结

PHP 8.2 带来了许多实用的新特性和改进:

主要新特性:

  • 只读类
  • 析取范式类型(DNF Types)
  • 独立的 null、true、false 类型
  • Trait 中的常量
  • 敏感参数隐藏
  • 新的 Random 扩展

需要注意的变化:

  • 动态属性已弃用
  • ${var} 字符串插值已弃用
  • utf8_encode()utf8_decode() 已弃用
  • strtolower()strtoupper() 不再受区域设置影响
  • 部分可调用模式已弃用

通过了解这些变化并遵循迁移建议,可以顺利地从 PHP 8.1 迁移到 PHP 8.2。