分类
php

从 PHP5 到 PHP8

这一篇文章,我们来说说 PHP 中的一些新的特性,包含 PHP7 到 PHP8。

8.x

根据官网的描述,增加了命名参数、注解、JIT 即时编译等特性。

JIT 即时编译

关于 JIT 即时编译,我们来看下面这张图:

左边是 PHP 采用 Opcache 的 执行流程,右边是加入了 JIT 的执行流程。一方面,JIT 是对 Opcache 的增强;另一方面 JIT 可以将优化后的 opcodes 直接编译成机器码,对于已经编译成机器码的,就跳过了 Zend 虚拟机交由 CPU 执行了。更多的细节,可以参考鸟哥的《PHP 8新特性之JIT简介》。

命名参数和任意类型

像 Go、Python 等语言都支持命名参数,增加代码的可读性(self-documented)。比如如下这段 Python 的代码:

def greeting(name="Tom"):
    print(name)

greeting(name="John")

从 PHP8 开始,也已经支持了这种语法,我们来看看:

<?php
function greeting($name="Tom"){
    echo $name . PHP_EOL;
}
greeting($name="John");

除此之外,使用这种方式,我们可以不那么在意参数的顺序。

变量类型可以声明为 mixed ,如果这个变量可以存储任意类型的值的话。对于类型声明可能为更加的统一,避免有的类型声明为 intstring ,而有的类型却因为可以是任意类型而不能使用类型声明。

<?php

function getData(mixed $params) {}

原生注解

一直以来,我们都希望 PHP 能够从语言层面支持注解的语法,以更好的服务 AOP 编程思想。在这个问题上,PHP 开发组赞同和不赞同的投票比是 51 比 1,可见引入这个特性是没有什么争议的。

在 8.0 以前,我们都是使用反射来实现注解的,如下形式:

<?php
/**
 * @RpcApi(method="POST", url="/member/cart/new")
 * @RpcAuthority(need="user.base")
 * @RpcParam(name="cityId", type="int", require=true, comment="城市ID")
 */
public function test() {}

现在我们可以使用原生的注解了,目前注解可以应用在如下实体中:

  • 函数(包括匿名函数和短函数)
  • 类(包括匿名类)、接口、特性
  • 类常量
  • 类属性
  • 类方法
  • 函数/方法参数

你可以在一行代码中使用多个注解,也可以在一个实体前面声明多行注解。

<?php
#[Attribute] 
class AttributeCustom {
    public function __construct($value) {
        var_dump($value);
    }
}
#[AttributeCustom("value")]
class Test {}
// 使用反射来获取类的注解
$clazz = new ReflectionClass('Test');
// 下面这行代码会输出 "value"
$instance = $clazz->getAttributes(AttributeCustom::class)[0]->newInstance();

和其他语言的注解的语法不太一样的是,关键字不是使用 @ 而是使用 #[]  来表示注解。为什么如此呢?对此社区中有非常多的讨论,其中一点是使用 @ 服务不容易检测属性的开始和结束,而使用 #[]  可以非常容易的知道属性的头尾。

所有的注解的类都是需要和其他的类一样事先导入的,这样做有三个好处:

  • 使用反射(Reflection API)可以直接获得这个注解的类的实例
  • 静态代码分析工具可以验证这个注解类的使用是否正确
  • IDE 可以支持这个注解类的属性或者参数的自动完成功能

构造器属性提升

这只是一种语法糖吧,第一眼看到的时候我心里是排斥的,这样的代码确实是简洁了,但可读性并不一定好,特别是当属性特别多的时候。

<?php
class Test{
    public function __construct(
        public string $name,
        public string $company,
        public int $age = 1,
    ){}
}

匹配表达式

然后我们来看一下匹配表达式,作为 switch 的一种替代语法,如下:

<?php
$operator = 'Add';
$result = match ($operator) {
    'Add' => fn($left, $right) => $left + $right,
    'Sub' => fn($left, $right) => $left - $right,
    'Mul' => fn($left, $right) => $left * $right,
    'Div' => fn($left, $right) => $left / $right,
};
var_dump($result(20, 10));		// 30

它有三个需要注意的地方:

  • 它是一个表达式,可以将返回值存储在变量中。
  • 使用严格匹配,即 "8" != 8 。
  • 仅仅支持单行,不需要加入 break 语法。

NullSafe 运算符

在 PHP 中,因为弱类型的缘故,写代码的时候要对类型特别的小心,经常要在代码中判断值是否存在、是否为空。在链式调用中,这种空的判断更是麻烦,现在我们可以使用 NullSafe 运算符来简化这种判断:

<?php
class Item {
    public function getValue() {}
}
class Session {
    public function __construct(
        public Item $item,
    ){}
}
class App {
    public function __construct(
        public Session $session
    ){}
}
(new App(new Session(new Item)))?->session?->item?->getValue();		// 使用 ? 来简化对象是否为空的判断

联合类型

一个变量可以定义为多种类型,这算是在弱类型和强类型之间留出一条空隙吧。这个在 TypeScript 和 PHP 这种弱类型向强类型靠拢的语言中比较实用:

<?php
class Test
{
    protected int|float $x;
    protected string|bool $y;
    protected ?Object $z;
}

发表评论

邮箱地址不会被公开。 必填项已用*标注