简介
PHP 8.2 引入了许多类型系统改进,包括只读类(Readonly Classes)、析取范式类型(DNF Types)、独立的 null、true、false 类型、敏感参数隐藏、新的 Random 扩展等。本文将介绍主要变化和如何从 PHP 8.1 迁移到 PHP 8.2。
参考资源
- 在线测试环境:https://onlinephp.io/
- 官方发布说明:https://www.php.net/releases/8.2/en.php
- 迁移指南:https://www.php.net/manual/en/migration82.php
- 更新详情:https://php.watch/versions/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 允许将 null、true、false 作为独立的类型使用:
// 函数总是返回 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 编码已被弃用:
BASE64UUENCODEHTML-ENTITIESQuoted-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。