PHP 8.1预计2021年11月正式发布,我从beta版本就开始试用,把一个新项目直接用PHP 8.1开发,用了两个多月,踩了不少坑,也积累了一些实战经验。

PHP 8.1是PHP 8.0之后的又一个重要版本,引入了很多期待已久的新特性:枚举(Enums)、只读属性(Readonly Properties)、纤维(Fibers)、交叉类型(Intersection Types)、never返回类型、first-class可调用语法、数组解包支持字符串键名、new初始化器、final类常量等。这些新特性能大大提升开发效率和代码质量,让PHP代码更现代、更安全、更有表达力。

但新特性也意味着新的坑。用不好的话,不仅不能提升效率,反而会引入bug,增加维护成本。我在试用过程中就踩了不少坑,有些是因为对新特性的理解不到位,有些是因为新特性和旧代码的兼容性问题,有些是因为工具链(IDE、静态分析工具、框架)还没有完全适配PHP 8.1。

本文总结我试用PHP 8.1新特性的踩坑经验和实战心得,聊聊每个新特性怎么用、有什么坑、需要注意什么,给打算升级PHP 8.1的朋友一些参考。

先说明一下:本文基于PHP 8.1 beta/RC版本的使用经验,正式版发布后可能会有一些变化。我主要用PHP 8.1做Web开发(Laravel框架),所以很多经验是基于Web开发场景的,其他场景可能有差异。

一、枚举(Enums):最实用的新特性,但也有不少坑

枚举是PHP 8.1最重要的新特性之一,也是我用得最多的新特性。它终于让PHP有了原生的枚举类型,不用再用类常量或者其他方式模拟了。

枚举的基本用法。

enum OrderStatus: string
{
    case PENDING = 'pending';
    case PAID = 'paid';
    case SHIPPED = 'shipped';
    case COMPLETED = 'completed';
    case CANCELLED = 'cancelled';
}

枚举可以有方法,可以实现接口,可以用trait,功能很强大。

我踩过的坑。

坑1:枚举不能用new实例化,也不能序列化。

刚开始用枚举的时候,我习惯性地想new OrderStatus(),结果报错了。枚举不能用new实例化,只能用预定义的case,比如OrderStatus::PAID

而且,枚举不能正常序列化。serialize(OrderStatus::PAID)会报错,因为枚举对象不是普通的对象。这在某些场景下会有问题,比如把枚举存在session里,或者缓存里,或者通过API传输。

解决方案:

  • 背枚举(有标量值的枚举)可以用->value获取标量值,序列化的时候存标量值,反序列化的时候用from()方法转回枚举。
  • 纯枚举(没有标量值的)可以用->name获取case名,序列化的时候存name,反序列化的时候用constant()或者反射转回枚举。
  • 不要直接序列化枚举对象,要转换成标量值再序列化。

坑2:枚举和数据库的交互需要自己处理。

枚举在PHP层面很好用,但和数据库交互的时候需要自己处理类型转换。数据库里存的是字符串或整数,查询出来是标量值,需要手动转换成枚举对象;写入数据库的时候,需要把枚举对象转换成标量值。

我用的是Laravel框架,Laravel 8还没有原生支持枚举类型转换(Laravel 9会支持),所以我需要自己写访问器和修改器:

// 访问器:从数据库取出时转换成枚举
public function getStatusAttribute($value): OrderStatus
{
    return OrderStatus::from($value);
}

// 修改器:写入数据库时转换成标量值
public function setStatusAttribute(OrderStatus $value): void
{
    $this->attributes['status'] = $value->value;
}

这样写有点麻烦,但能用。等Laravel 9原生支持枚举类型转换后,就不用自己写了。

如果用的是其他框架或者原生PHP,也需要自己处理这个转换,或者用ORM的类型转换功能。

坑3:枚举的比较要用===,不要用==。

枚举对象的比较,要用===(严格比较),不要用==(松散比较)。因为==会尝试类型转换,可能会有意外的结果。

比如:

OrderStatus::PAID === OrderStatus::PAID; // true
OrderStatus::PAID == OrderStatus::PAID; // true,但不推荐
OrderStatus::PAID === 'paid'; // false,类型不同
OrderStatus::PAID == 'paid'; // true,因为松散比较会转换,但这不是你想要的

正确的做法是,枚举和枚举比较用===,枚举和标量值比较的时候,先把枚举转换成标量值(->value),或者用标量值转换成枚举(from())。

坑4:枚举的case名不能动态创建,也不能有重复的标量值。

枚举的case是在定义时固定的,不能在运行时动态添加。如果你的值是动态的(比如存在数据库里的字典),就不适合用枚举。

而且,背枚举的标量值必须唯一,不能重复。比如:

enum Test: string
{
    case A = 'a';
    case B = 'a'; // 报错,标量值重复
}

这个是合理的,因为from()方法需要通过标量值唯一确定一个case,如果有重复就会有歧义。

坑5:枚举不能有属性,只能有方法。

枚举不能有普通的属性(成员变量),只能有方法。刚开始我想给枚举加个属性存中文名称,结果报错了。

解决方案是用方法返回:

enum OrderStatus: string
{
    case PENDING = 'pending';
    case PAID = 'paid';
    
    public function label(): string
    {
        return match($this) {
            self::PENDING => '待支付',
            self::PAID => '已支付',
        };
    }
}

这样用OrderStatus::PAID->label()就能获取中文名称了。

我的使用建议。

枚举是PHP 8.1最实用的新特性,强烈推荐使用。但要注意:

  1. 只用于值固定的场景,不要用于动态值。
  2. 和数据库、缓存、API交互时,注意类型转换,不要直接序列化枚举对象。
  3. 比较用===,不要用==。
  4. 用方法代替属性,存储额外信息。
  5. 注意框架和工具链的兼容性,有些框架和工具还没有完全适配枚举。

二、只读属性(Readonly Properties):简单好用,但有一些限制

只读属性是PHP 8.1另一个很实用的新特性,用readonly关键字声明,属性只能在初始化的时候赋值,之后不能修改。

基本用法。

class User
{
    public function __construct(
        public readonly int $id,
        public readonly string $name,
        public readonly string $email,
    ) {}
}

和构造函数提升(Constructor Promotion)一起用,非常简洁。

我踩过的坑。

坑1:只读属性不能有默认值,也不能在构造函数外赋值。

只读属性只能在声明的时候(如果有默认值的话,但实际上不能有)或者构造函数里赋值。我刚开始想给只读属性设默认值,结果报错了。

class Test
{
    public readonly string $name = 'default'; // 报错,只读属性不能有默认值
}

而且,只读属性不能在构造函数外的其他方法里赋值,即使是在同一个类里也不行。

class Test
{
    public readonly string $name;
    
    public function __construct()
    {
        $this->name = 'test'; // 可以,构造函数里可以赋值
    }
    
    public function setName(string $name): void
    {
        $this->name = $name; // 报错,构造函数外不能赋值
    }
}

这个是合理的,因为只读属性的语义就是只能初始化一次,之后不能修改。如果需要在其他方法里赋值,就不应该用只读属性。

坑2:只读属性不能是static的,也不能在trait里声明。

只读属性只能是实例属性,不能是静态属性。而且,PHP 8.1的只读属性不能在trait里声明(这个限制在后续版本可能会放宽)。

我刚开始想在trait里用只读属性,结果报错了,只能改成普通属性,或者把属性移到使用trait的类里。

坑3:只读属性和继承的关系。

父类的只读属性,子类不能覆盖,也不能修改。如果子类重新声明同名的只读属性,会报错。

而且,父类的构造函数赋值了只读属性,子类的构造函数调用parent::__construct()之后,也不能再修改这个属性了。

这个是合理的,因为只读属性一旦初始化就不能修改,子类也不能例外。

坑4:克隆对象的时候,只读属性也不能修改。

PHP的clone()魔术方法可以在克隆对象的时候修改属性,但只读属性在clone()里也不能修改。我刚开始想在克隆的时候修改只读属性,结果报错了。

如果需要在克隆的时候修改属性,那这个属性就不应该是只读的。

我的使用建议。

只读属性非常适合值对象(Value Object)、DTO(数据传输对象)、不可变对象等场景。用了只读属性之后,不用写一堆getter,代码简洁很多,而且对象是不可变的,更安全。

但要注意:

  1. 只读属性只能在构造函数里赋值,需要修改的属性不要用readonly。
  2. 不能有默认值,不能是static的,不能在trait里声明(PHP 8.1)。
  3. 继承的时候注意,子类不能覆盖父类的只读属性。
  4. 克隆的时候不能修改只读属性。

总体来说,只读属性简单好用,限制也都是合理的,推荐在适合的场景使用。

三、纤维(Fibers):强大但生态还不成熟,谨慎使用

纤维是PHP 8.1最令人兴奋的新特性,它给PHP带来了原生的协程支持。但也是最需要谨慎使用的新特性,因为生态还不成熟。

基本用法。

$fiber = new Fiber(function (): void {
    echo "Fiber开始\n";
    Fiber::suspend();
    echo "Fiber恢复\n";
});

$fiber->start();
echo "主程序\n";
$fiber->resume();

纤维可以创建、暂停、恢复,是用户态的协程,开销很小。

我踩过的坑。

坑1:纤维本身不提供调度器,需要自己写或者用第三方库。

这是最大的坑。很多人以为PHP有了纤维,就可以像Go语言那样写协程了,直接go function()就行。但实际上,纤维只是一个底层的协程原语,它只提供了创建、暂停、恢复的能力,不提供调度器。

也就是说,你创建了多个纤维,它们不会自动并发执行,你需要自己写调度器来管理它们的执行,决定什么时候暂停哪个、恢复哪个。

自己写调度器是很复杂的,需要处理事件循环、IO多路复用、定时器、异常处理等。对于普通开发者来说,不建议自己写调度器,应该用基于纤维的第三方协程框架,比如ReactPHP、Swoole(Swoole也在适配纤维)等。

我刚开始以为有了纤维就能直接写异步代码了,结果发现没有调度器根本用不起来,只能做一些简单的演示。真正要在项目里用纤维做异步编程,还需要等生态成熟,或者用成熟的协程框架。

坑2:纤维不能跨线程,也不能在多个线程间传递。

纤维是在单个PHP进程里的,不能跨线程,也不能在多个线程间传递。PHP的多线程(pthreads扩展)本身用得就不多,这个坑影响不大,但需要知道。

坑3:纤维的异常处理需要注意。

纤维里抛出的异常,会在调用start()或者resume()的时候抛出,需要在主程序里捕获。如果不捕获,会导致整个程序崩溃。

$fiber = new Fiber(function (): void {
    throw new RuntimeException('fiber error');
});

try {
    $fiber->start();
} catch (RuntimeException $e) {
    echo "捕获到异常: " . $e->getMessage();
}

而且,纤维里的异常如果没有被捕获,纤维就会进入终止状态,不能再恢复了。

坑4:纤维和现有代码的兼容性。

纤维是PHP 8.1的新特性,很多现有代码、框架、库还没有适配纤维。在现有项目里引入纤维,可能会有兼容性问题。

比如,很多库是同步阻塞的,在纤维里调用这些库的阻塞函数,会阻塞整个进程,其他纤维也无法执行,就失去了协程的意义。要真正发挥纤维的作用,需要用非阻塞的库,或者把阻塞操作放到线程池里。

而且,很多框架的生命周期、请求处理流程是同步的,引入纤维需要改造框架,成本很高。

坑5:纤维的调试和性能分析比较困难。

纤维的执行流程是交错的,调试起来比普通的同步代码困难。断点、堆栈跟踪、日志等,都需要考虑纤维的上下文。性能分析也比较困难,因为多个纤维交错执行,传统的性能分析工具可能无法准确分析每个纤维的执行时间。

我的使用建议。

纤维是PHP 8.1最有前景的新特性,它给PHP带来了原生协程的基础,未来会深刻改变PHP的异步编程生态。但在目前(2021年),纤维的生态还不成熟,调度器、非阻塞库、框架适配都还在发展中。

我的建议是:

  1. 学习和了解纤维的原理,这是未来的趋势,迟早会用到。
  2. 在个人项目、测试项目里试用,熟悉纤维的用法和特性。
  3. 生产环境谨慎使用,除非你用的是成熟的基于纤维的协程框架,否则不要自己在生产环境里用纤维写异步代码,容易踩坑。
  4. 关注生态发展,等基于纤维的协程框架、非阻塞库、框架适配都成熟了,再大规模使用。

简单来说,纤维是未来,但现在还不是大规模使用的时候。保持关注,提前学习,等生态成熟了再用。

四、交叉类型(Intersection Types):实用但用得不多

交叉类型用&连接多个类型,表示参数或返回值必须同时满足所有类型。比如A&B表示对象必须同时实现了A和B两个接口。

基本用法。

interface Logger
{
    public function log(string $message): void;
}

interface Formatter
{
    public function format(string $message): string;
}

function process(Logger&Formatter $obj): void
{
    $formatted = $obj->format('hello');
    $obj->log($formatted);
}

我踩过的坑。

坑1:交叉类型只能用接口和类,不能用标量类型。

交叉类型的组成部分只能是接口和类,不能用int、string、array等标量类型,也不能用void、mixed、never等特殊类型。

我刚开始想写int&string,结果报错了。交叉类型是用于对象的,表示对象同时具有多种类型的能力,标量类型不能交叉。

坑2:不能有冲突的方法。

如果交叉类型的多个接口里有同名但签名不同的方法,会导致冲突,这样的交叉类型不能用。

interface A
{
    public function test(int $a): void;
}

interface B
{
    public function test(string $a): void;
}

function foo(A&B $obj): void {} // 报错,方法签名冲突

这个是合理的,因为如果两个接口的同名方法签名不同,实现类无法同时满足两个接口的要求,这样的交叉类型没有意义。

坑3:交叉类型和联合类型不能混用。

PHP 8.1的交叉类型不能和联合类型(|)混用。比如(A&B)|C是不允许的。这个限制在后续版本可能会放宽(PHP 8.2支持了DNF类型),但PHP 8.1不行。

我刚开始想写一个复杂的类型,既有交叉又有联合,结果报错了,只能拆成多个函数或者用其他方式处理。

我的使用建议。

交叉类型在某些场景下很实用,比如需要一个对象同时实现多个接口的时候。但在实际开发中,这种场景并不多,大部分时候用一个接口就够了,或者可以定义一个组合接口继承多个接口。

我的建议是:

  1. 如果经常需要同时用某几个接口,可以定义一个组合接口继承它们,比每次写交叉类型更清晰。
  2. 偶尔需要的场景,可以用交叉类型,简洁方便。
  3. 注意交叉类型的限制,只能用接口和类,不能有冲突的方法,不能和联合类型混用。

总体来说,交叉类型是一个实用但不常用的新特性,了解就行,需要的时候用。

五、never返回类型:简单好用,注意适用场景

never是PHP 8.1新增的返回类型,表示函数永远不会返回,要么抛出异常,要么调用exit/die终止程序。

基本用法。

function abort(string $message): never
{
    throw new RuntimeException($message);
}

function shutdown(): never
{
    echo "程序结束\n";
    exit;
}

我踩过的坑。

坑1:never函数必须不返回,否则报错。

这个是最基本的,但我刚开始还是踩了坑。我写了一个函数,声明了never返回类型,但函数里有一个分支是正常返回的(有return语句,或者执行到函数末尾),结果报错了。

function test(bool $flag): never
{
    if ($flag) {
        throw new RuntimeException('error');
    }
    // 这里没有throw也没有exit,函数会正常返回,报错
}

never函数的所有分支都必须是抛出异常或者终止程序,不能有正常返回的路径。如果有正常返回的可能,就不要用never返回类型,用void或者其他类型。

坑2:never不能和其他类型联合。

never是PHP类型系统里的底部类型,它是所有类型的子类型。所以never不能和其他类型组成联合类型,比如never|int是不允许的,因为never已经包含在所有类型里了。

这个限制一般不会遇到,知道就行。

坑3:never函数的调用之后的代码是不可达的。

调用了never返回类型的函数之后,后面的代码是不可达的,因为这个函数永远不会返回。静态分析工具和IDE会提示这一点。

function test(): void
{
    abort('error'); // never函数
    echo "这里不会执行"; // 不可达代码,IDE会提示
}

这个是合理的,因为never函数永远不会返回,后面的代码自然不会执行。如果后面还有代码,说明可能有逻辑错误。

我的使用建议。

never返回类型简单好用,适合抛出异常、终止程序、无限循环的函数。用了never之后,静态分析工具和IDE能更好地理解代码,检测不可达代码,提升代码质量。

建议在合适的场景使用,比如:

  • abort()、throw_exception()等抛出异常的函数
  • shutdown()、exit()等终止程序的函数
  • run_forever()等无限循环的函数

注意确保函数的所有分支都不会正常返回,否则不要用never。

六、其他新特性的踩坑经验

除了上面几个重要的新特性,PHP 8.1还有一些小的新特性,也有一些坑。

1. first-class可调用语法。

PHP 8.1引入了first-class可调用语法,可以用函数名(...)获取可调用对象:

$callable = strlen(...);
echo $callable('hello'); // 5

踩坑:这个语法的...是语法的一部分,不是参数展开,不要和可变参数混淆。而且,这个语法只能用于获取可调用对象,不能用于调用函数。刚开始我写strlen(...'hello'),结果报错了,正确的写法是先获取可调用对象再调用,或者直接用普通调用方式。

2. 数组解包支持字符串键名。

PHP 8.1之前,数组解包(...)只支持整数键名的数组,PHP 8.1开始支持字符串键名:

$array1 = ['a' => 1, 'b' => 2];
$array2 = ['c' => 3, 'd' => 4];
$result = [...$array1, ...$array2];

踩坑:如果有重复的字符串键名,后面的会覆盖前面的,和array_merge的行为一致。这个和整数键名的行为不同(整数键名的解包不会覆盖,而是重新索引)。刚开始我以为字符串键名也不会覆盖,结果被覆盖了,调试了半天才发现。

3. new初始化器。

PHP 8.1允许在参数默认值、属性默认值、常量等地方使用new创建对象:

class Logger
{
    private Formatter $formatter = new DefaultFormatter();
    
    public function log(string $message, Formatter $formatter = new DefaultFormatter()): void
    {
        // ...
    }
}

踩坑:new初始化器里的对象,每次调用函数或者每次实例化类的时候都会创建新的对象,不是共享的。比如参数默认值的new DefaultFormatter(),每次调用函数如果不传这个参数,都会创建一个新的DefaultFormatter对象。如果对象创建成本高,或者需要共享状态,就要注意这一点。

而且,new初始化器里不能用函数调用、不能用变量,只能用new创建对象,参数也必须是常量表达式。

4. final类常量。

PHP 8.1允许在类常量前加final关键字,表示这个常量不能被子类覆盖:

class ParentClass
{
    final public const VERSION = '1.0';
}

踩坑:final类常量只能在类里用,不能在接口里用(接口的常量本来就不能被覆盖,不需要final)。而且,final类常量必须是public的,不能是private或protected的(因为private的常量子类本来就访问不到,不需要final;protected的常量子类可以覆盖,但final的语义是不能覆盖,这个在PHP 8.1里可能有bug或者限制,我遇到过报错,建议只在public常量上用final)。

5. 八进制整数的显式表示。

PHP 8.1引入了显式的八进制前缀0o(或0O),和二进制的0b、十六进制的0x一致:

$octal = 0o123; // 八进制,等于十进制的83

踩坑:这个没什么坑,就是注意0o是数字0加字母o,不是字母O(虽然大写O也支持),不要写错了。旧的0前缀的八进制写法依然支持,但推荐用新的0o写法,更清晰。

七、升级PHP 8.1的整体踩坑经验

除了具体新特性的坑,升级PHP 8.1整体也有一些坑需要注意。

坑1:不兼容的变更。

PHP 8.1有一些不兼容的变更,升级之前需要检查代码:

  • 弃用了Serializable接口的隐式序列化,实现了Serializable的类需要同时实现serialize()unserialize()方法,否则会有弃用警告。
  • 弃用了autoloader里的requireinclude返回值。
  • 弃用了htmlspecialchars()htmlentities()的默认编码(默认从ISO-8859-1改成了UTF-8,但显式指定编码的不受影响)。
  • 弃用了filtervar()FILTERSANITIZESTRING过滤器,用FILTERSANITIZEFULLSPECIAL_CHARS代替。
  • 弃用了mysqli的一些函数,比如mysqli::getclientinfo()等。
  • 内部方法的返回类型开始逐步添加,有些类的方法签名变了,如果你的类继承了这些内部类,需要调整方法签名。

升级之前,一定要仔细看官方的迁移指南,检查代码里有没有用到弃用的功能,提前修改。建议先在测试环境升级,跑测试,修复所有警告和错误,再灰度上线。

坑2:框架和库的兼容性。

PHP 8.1是新的大版本,很多框架和库还没有完全适配。我用的是Laravel 8,Laravel 8对PHP 8.1的支持还不是完全的,有些地方会有警告或者错误,需要升级到Laravel 9(Laravel 9正式支持PHP 8.1)。

其他框架和库也是一样,升级PHP 8.1之前,要检查你用的框架和库是否支持PHP 8.1,如果不支持,需要升级框架和库,或者等它们适配之后再升级PHP。

我踩的坑是,直接把项目升到PHP 8.1,结果Laravel 8的很多地方报弃用警告,甚至有错误,折腾了很久才解决,后来干脆把Laravel也升到了9.x,才彻底解决。

坑3:IDE和静态分析工具的适配。

PHP 8.1的新特性(枚举、只读属性、纤维、交叉类型等),IDE和静态分析工具需要更新才能支持。我用的是PhpStorm,需要升级到最新版本才能完整支持PHP 8.1的语法,否则会有很多红色波浪线,代码提示也不准确。

静态分析工具(比如PHPStan、Psalm)也需要升级到支持PHP 8.1的版本,否则会误报很多错误。

升级PHP 8.1之前,记得把IDE、静态分析工具、代码质量工具都升级到最新版本,避免因为工具不支持而产生很多误报。

坑4:扩展的兼容性。

PHP 8.1是新的大版本,很多PHP扩展需要重新编译或者升级才能支持。如果你用了一些不常用的扩展,或者自己编译的扩展,升级PHP 8.1之后可能会发现扩展用不了,需要重新编译或者等扩展作者更新。

我用的都是常用扩展(PDO、mbstring、openssl、gd、redis等),这些扩展官方都及时更新了,没有遇到问题。但如果用了小众扩展,就要提前检查兼容性。

坑5:性能变化。

PHP 8.1在性能上有一些优化,但也有一些新特性可能会影响性能。比如,枚举对象的创建、只读属性的检查、纤维的开销等,都可能有性能影响。

总体来说,PHP 8.1的性能和PHP 8.0差不多,某些场景有提升,某些场景可能略有下降。升级之后,建议做一下性能测试,确认没有明显的性能下降。如果有性能问题,再针对性优化。

八、升级建议和最佳实践

最后,总结一下升级PHP 8.1的建议和最佳实践。

1. 不要急着升级,等生态成熟。

PHP 8.1刚发布的时候,很多框架、库、工具、扩展还没有完全适配,急着升级会踩很多坑。建议等正式版发布3-6个月之后,生态基本成熟了再升级。如果是新项目,可以直接用PHP 8.1,但要做好踩坑的准备。如果是老项目,建议等框架和库都适配之后再升级。

2. 先在测试环境升级,充分测试。

升级一定要先在测试环境进行,不要直接在生产环境升级。在测试环境升级之后,跑完整的测试套件,包括单元测试、集成测试、功能测试,确保所有功能正常。还要检查日志,有没有警告、错误、弃用通知。

测试环境验证没问题之后,再灰度上线,先在一小部分服务器上升级,观察一段时间,确认没有问题再全量上线。

3. 逐步使用新特性,不要一下子全用。

升级到PHP 8.1之后,不要一下子把所有新特性都用上。建议先从简单、成熟、风险低的新特性开始,比如只读属性、never返回类型、first-class可调用语法等。枚举也可以用,但要注意和数据库、缓存的交互。纤维、交叉类型等生态还不成熟的特性,先在非核心代码里试用,等熟悉了、生态成熟了再大规模使用。

逐步使用新特性,遇到问题也容易定位和回滚,风险更低。

4. 新特性要配合静态分析工具使用。

PHP 8.1的很多新特性(枚举、只读属性、交叉类型、never等),配合静态分析工具(PHPStan、Psalm)使用效果更好。静态分析工具能帮你发现很多类型错误、逻辑错误,尤其是新特性的使用错误。

建议升级PHP 8.1的同时,也引入或者升级静态分析工具,把静态分析加入CI流程,在代码提交的时候就检查错误,保证代码质量。

5. 关注官方文档和社区动态。

PHP 8.1是新的版本,官方文档还在完善中,社区也在不断分享使用经验和踩坑总结。建议关注PHP官方文档、官方博客、PHP社区(比如Laravel社区、Symfony社区)、技术博客,及时了解新特性的最佳实践、已知问题、升级指南等。

遇到问题的时候,先搜一下有没有人遇到过同样的问题,有没有解决方案,不要自己闷头调试,能省很多时间。

九、写在最后

PHP 8.1是一个令人兴奋的版本,枚举、只读属性、纤维、交叉类型、never返回类型等新特性,让PHP变得更现代、更安全、更有表达力。作为PHP开发者,我们应该积极学习和拥抱这些新特性,用它们写出更好的代码。

但新特性也意味着新的坑。我在试用PHP 8.1的过程中,踩了不少坑,有些是因为对新特性理解不到位,有些是因为生态还不成熟,有些是因为升级过程中的兼容性问题。本文总结了这些踩坑经验,希望能给打算升级PHP 8.1的朋友一些参考,让大家少走弯路。

总的来说,PHP 8.1的新特性是值得学习和使用的,大部分坑都是可以避免的,只要我们充分了解新特性的特性和限制,做好测试,逐步使用,就能享受新特性带来的好处。

PHP在不断进步,从PHP 7的性能革命,到PHP 8.0的联合类型、命名参数、match表达式,再到PHP 8.1的枚举、只读属性、纤维,PHP越来越现代,越来越强大。作为PHP开发者,我们也要跟着进步,学习新特性,用新特性写出更好的代码,构建更好的产品。

最后用一句话结束本文:"技术在不断发展,学习是终身的事情。"愿我们都能保持学习的热情,跟上技术的步伐,成为更好的开发者。