简介
PHP 8.1 引入了许多重要的新特性,包括枚举(Enums)、只读属性(Readonly Properties)、一等可调用语法(First-class Callable Syntax)、Fibers、交叉类型(Intersection Types)等。本文将介绍主要变化和如何从 PHP 8.0 迁移到 PHP 8.1。
参考资源
- 在线测试环境:https://onlinephp.io/
- 官方发布说明:https://www.php.net/releases/8.1/en.php
- 迁移指南:https://www.php.net/manual/en/migration81.php
- 更新详情:https://php.watch/versions/8.1
PHP 8.1 新特性
枚举(Enums)
PHP 8.1 原生支持枚举类型,取代了之前使用类常量的方式:
// 纯枚举(Pure Enum)
enum Status {
case Draft;
case Published;
case Archived;
}
// 回退枚举(Backed Enum)- 带有标量值
enum Status: string {
case Draft = 'draft';
case Published = 'published';
case Archived = 'archived';
}
// 使用枚举作为类型提示
class BlogPost {
public function __construct(
public Status $status = Status::Draft
) {}
}
$post = new BlogPost();
echo $post->status->value; // 'draft'
// 枚举可以有方法
enum Color: string {
case Red = '#FF0000';
case Green = '#00FF00';
case Blue = '#0000FF';
public function label(): string {
return match($this) {
self::Red => '红色',
self::Green => '绿色',
self::Blue => '蓝色',
};
}
}
echo Color::Red->label(); // '红色'
// 从值获取枚举
$status = Status::from('draft'); // Status::Draft
$status = Status::tryFrom('invalid'); // null(不会抛出异常)
枚举的特性:
- 枚举可以实现接口
- 枚举可以使用 trait
- 枚举不能被实例化(
new Status()是非法的) - 枚举 case 是单例
只读属性(Readonly Properties)
属性可以标记为 readonly,只能初始化一次:
class User {
public readonly string $id;
public readonly string $name;
public function __construct(string $id, string $name) {
$this->id = $id;
$this->name = $name;
}
}
$user = new User('123', 'John');
echo $user->id; // '123'
$user->id = '456';
// Fatal error: Cannot modify readonly property User::$id
// 结合构造函数提升使用
class Product {
public function __construct(
public readonly string $sku,
public readonly float $price,
) {}
}
只读属性的特性:
- 只能初始化一次,初始化后不可修改
- 必须有类型声明
- 不能有默认值(除非在构造函数中设置)
- 可以在构造函数或声明时初始化
一等可调用语法(First-class Callable Syntax)
使用 ... 语法创建闭包,替代 Closure::fromCallable():
// 函数转闭包
$strlen = strlen(...);
echo $strlen('hello'); // 5
// 等同于
$strlen = Closure::fromCallable('strlen');
// 方法转闭包
class Calculator {
public function add(int $a, int $b): int {
return $a + $b;
}
public static function multiply(int $a, int $b): int {
return $a * $b;
}
public function __invoke(int $x): int {
return $x * 2;
}
}
$calc = new Calculator();
// 实例方法
$addFn = $calc->add(...);
echo $addFn(3, 5); // 8
// 静态方法
$multiplyFn = Calculator::multiply(...);
echo $multiplyFn(3, 4); // 12
// __invoke 方法
$invokeFn = $calc(...);
echo $invokeFn(10); // 20
// 在数组函数中使用
$words = ['apple', 'banana', 'cherry'];
$lengths = array_map(strlen(...), $words); // [5, 6, 6]
Fibers(纤程)
Fibers 是轻量级的协程,允许暂停和恢复代码执行:
$fiber = new Fiber(function(): void {
echo "1. Fiber 开始\n";
$value = Fiber::suspend('暂停值');
echo "3. Fiber 恢复,收到: $value\n";
});
echo "0. 主程序开始\n";
$suspended = $fiber->start(); // 启动 Fiber
echo "2. 主程序收到: $suspended\n";
$fiber->resume('恢复值'); // 恢复 Fiber
echo "4. 主程序结束\n";
// 输出:
// 0. 主程序开始
// 1. Fiber 开始
// 2. 主程序收到: 暂停值
// 3. Fiber 恢复,收到: 恢复值
// 4. 主程序结束
Fibers 主要用于异步框架(如 ReactPHP、Amp),普通应用开发较少直接使用。
交叉类型(Intersection Types)
使用 & 运算符要求参数同时满足多个类型:
// 参数必须同时实现 Iterator 和 Countable
function processItems(Iterator&Countable $items): int {
foreach ($items as $item) {
// 处理项目
}
return count($items);
}
// ArrayIterator 同时实现了这两个接口
$items = new ArrayIterator([1, 2, 3]);
echo processItems($items); // 3
// 更复杂的示例
interface Loggable {
public function log(): void;
}
interface Serializable {
public function serialize(): string;
}
function store(Loggable&Serializable $object): void {
$object->log();
$data = $object->serialize();
// 存储数据
}
never 返回类型
表示函数永远不会返回(总是抛出异常或终止程序):
function redirect(string $url): never {
header("Location: $url");
exit();
}
function throwError(string $message): never {
throw new RuntimeException($message);
}
// 静态分析工具可以检测 never 之后的死代码
redirect('/home');
echo "这行代码永远不会执行"; // IDE 会标记为死代码
final 类常量
类常量可以标记为 final,防止子类覆盖:
class ParentClass {
final public const VERSION = '1.0';
public const NAME = 'Parent';
}
class ChildClass extends ParentClass {
// ❌ Fatal error: Cannot override final constant
public const VERSION = '2.0';
// ✅ 可以覆盖非 final 常量
public const NAME = 'Child';
}
初始化器中的 new 表达式
可以在默认参数、属性默认值和常量中使用 new 表达式:
// 默认参数
class Logger {
public function __construct(
private Formatter $formatter = new DefaultFormatter(),
) {}
}
// 属性默认值
class Config {
public DateTimeImmutable $createdAt = new DateTimeImmutable();
}
// 属性(PHP 8.0 的属性也支持)
#[Attribute]
class Route {
public function __construct(
public string $path,
public array $methods = ['GET'],
) {}
}
class Controller {
#[Route('/users', methods: ['GET', 'POST'])]
public function users() {}
}
字符串键数组解包
现在可以对字符串键数组使用扩展运算符:
$array1 = ['a' => 1, 'b' => 2];
$array2 = ['c' => 3, 'd' => 4];
// PHP 8.1 之前:只能用于数字键数组
// PHP 8.1:支持字符串键
$merged = [...$array1, ...$array2];
// ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4]
// 后面的值会覆盖前面的
$array3 = ['a' => 'override'];
$result = [...$array1, ...$array3];
// ['a' => 'override', 'b' => 2]
显式八进制表示法
新增 0o 前缀表示八进制数:
// 传统八进制表示法(仍然有效)
$permissions = 0755;
// PHP 8.1 新的显式表示法
$permissions = 0o755;
// 两者等价
var_dump(0o16); // int(14)
var_dump(016); // int(14)
// 0o 更清晰,避免与数字 0 开头的误解
PHP 8.1 新增函数
array_is_list()
检查数组是否为列表(连续的从 0 开始的整数键):
// 是列表
array_is_list([]); // true
array_is_list(['a', 'b', 'c']); // true
array_is_list([0 => 'a', 1 => 'b']); // true
// 不是列表
array_is_list([1 => 'a', 2 => 'b']); // false(不从 0 开始)
array_is_list(['a' => 1, 'b' => 2]); // false(字符串键)
array_is_list([0 => 'a', 2 => 'b']); // false(不连续)
fsync() 和 fdatasync()
强制将文件数据写入磁盘:
$file = fopen('data.txt', 'w');
fwrite($file, 'important data');
// fsync: 同步文件数据和元数据
fsync($file);
// fdatasync: 只同步文件数据(更快,不包括元数据)
fdatasync($file);
fclose($file);
Sodium XChaCha20 函数
新增 XChaCha20 加密函数:
// 生成密钥
$key = sodium_crypto_stream_xchacha20_keygen();
// XChaCha20 加密
$nonce = random_bytes(SODIUM_CRYPTO_STREAM_XCHACHA20_NONCEBYTES);
$encrypted = sodium_crypto_stream_xchacha20_xor($message, $nonce, $key);
新的哈希算法
支持 xxHash 和 MurmurHash3:
// xxHash(非常快的非加密哈希)
echo hash('xxh3', 'data');
echo hash('xxh64', 'data');
echo hash('xxh128', 'data');
// MurmurHash3
echo hash('murmur3a', 'data');
echo hash('murmur3c', 'data');
echo hash('murmur3f', 'data');
枚举相关函数
enum Status: string {
case Active = 'active';
case Inactive = 'inactive';
}
// 获取所有 case
$cases = Status::cases();
// [Status::Active, Status::Inactive]
// 回退枚举的额外方法
$status = Status::from('active'); // Status::Active
$status = Status::tryFrom('unknown'); // null
弃用特性
向非可空参数传递 null
向内置函数的非可空参数传递 null 已被弃用:
// ❌ PHP 8.1 弃用警告
strlen(null);
str_contains(null, 'test');
trim(null);
// ✅ 修复方法
strlen($value ?? '');
str_contains($value ?? '', 'test');
trim($value ?? '');
Serializable 接口弃用
Serializable 接口已被弃用,使用 __serialize() 和 __unserialize() 替代:
// ❌ 已弃用
class OldWay implements Serializable {
public function serialize(): string {
return serialize($this->data);
}
public function unserialize(string $data): void {
$this->data = unserialize($data);
}
}
// ✅ 推荐方式
class NewWay {
public function __serialize(): array {
return ['data' => $this->data];
}
public function __unserialize(array $data): void {
$this->data = $data['data'];
}
}
隐式浮点数转整数弃用
非整数浮点数隐式转换为整数时会产生弃用警告:
// ❌ 弃用警告
$arr = [];
$arr[3.14] = 'value'; // 3.14 会被转为 3
// ✅ 显式转换
$arr[(int)3.14] = 'value';
日期函数弃用
// ❌ 已弃用
strftime('%Y-%m-%d', time());
gmstrftime('%Y-%m-%d', time());
strptime('2024-01-01', '%Y-%m-%d');
date_sunrise($timestamp);
date_sunset($timestamp);
// ✅ 替代方案
// 使用 IntlDateFormatter
$formatter = new IntlDateFormatter(
'zh_CN',
IntlDateFormatter::FULL,
IntlDateFormatter::NONE
);
echo $formatter->format(time());
// 或使用 DateTime
$date = new DateTime();
echo $date->format('Y-m-d');
mhash*() 函数弃用
// ❌ 已弃用
mhash(MHASH_MD5, 'data');
mhash_keygen_s2k(MHASH_SHA1, 'password', 'salt', 16);
// ✅ 使用 hash() 函数
hash('md5', 'data');
hash_pbkdf2('sha1', 'password', 'salt', 1000, 16);
PDO::FETCH_SERIALIZE 弃用
// ❌ 已弃用
$stmt->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_SERIALIZE, MyClass::class);
// ✅ 使用 __serialize()/__unserialize() 魔术方法
auto_detect_line_endings INI 弃用
// ❌ 已弃用
ini_set('auto_detect_line_endings', true);
// ✅ 手动处理行结束符
$content = str_replace(["\r\n", "\r"], "\n", $content);
兼容性变化
资源类型迁移为对象
多个扩展的资源类型被迁移为对象:
// finfo
$finfo = finfo_open(FILEINFO_MIME);
var_dump($finfo); // PHP 8.0: resource, PHP 8.1: finfo object
// FTP
$ftp = ftp_connect('ftp.example.com');
var_dump($ftp); // FTP\Connection object
// IMAP
$imap = imap_open('{imap.example.com:993/imap/ssl}', 'user', 'pass');
var_dump($imap); // IMAP\Connection object
// LDAP
$ldap = ldap_connect('ldap.example.com');
var_dump($ldap); // LDAP\Connection object
// PostgreSQL
$pg = pg_connect('host=localhost dbname=test');
var_dump($pg); // PgSql\Connection object
继承方法中的静态变量
继承方法中的静态变量现在与父类共享:
class A {
public static function counter() {
static $i = 0;
return ++$i;
}
}
class B extends A {}
var_dump(A::counter()); // int(1)
var_dump(A::counter()); // int(2)
var_dump(B::counter());
// PHP 8.0: int(1)(B 有自己的计数器)
// PHP 8.1: int(3)(B 共享 A 的计数器)
HTML 实体函数默认处理单引号
// PHP 8.0: 默认 ENT_COMPAT,不转义单引号
// PHP 8.1: 默认 ENT_QUOTES | ENT_SUBSTITUTE
$str = "It's a \"test\"";
echo htmlspecialchars($str);
// PHP 8.0: It's a "test"
// PHP 8.1: It's a "test"
性能改进
PHP 8.1 带来了显著的性能提升:
- Symfony Demo: 提升约 23%
- WordPress: 提升约 3.5%
- 继承缓存优化
- 快速类名解析
- 内置函数优化
- ARM64 JIT 后端支持
总结
PHP 8.1 带来了许多实用的新特性和改进:
主要新特性:
- 枚举(Enums)
- 只读属性(Readonly Properties)
- 一等可调用语法
- Fibers(纤程)
- 交叉类型(Intersection Types)
- never 返回类型
- final 类常量
- 初始化器中的 new 表达式
- 字符串键数组解包
需要注意的变化:
- 向非可空参数传递 null 已弃用
- Serializable 接口已弃用
- 隐式浮点数转整数已弃用
- 多个资源类型迁移为对象
- 继承方法中的静态变量行为变化
- 日期相关函数弃用
通过了解这些变化并遵循迁移建议,可以顺利地从 PHP 8.0 迁移到 PHP 8.1。