分享到: 分享到QQ  分享到Twitter

作者: BigLoser    访问次数: 285 创建时间: 2022-12-29 00:46:47 更新时间: 2024-05-09 00:30:55

PHP 8 是PHP的一次重大更新,它引入了一些新特性和性能优化,包括注解(attributes)、 match 表达式、 instanceof 运算符、 new 运算符、全新的 JIT 编译器等等。

 

注解(attributes)提供了一种向类、方法、函数、参数、属性和类常量中添加元数据的方法。Attributes 注解类似于其他一些语言所支持的 annotations 注解。new 运算符仅从 PHP 8.1 开始可用,可用于默认参数值、静态变量的初始化设置和属性参数。 match 表达式是一种新的控制结构,它使用身份运算符(identity operator)对带有多分支条件表达式的主题表达式进行匹配,并执行匹配的分支。 instanceof 运算符以前只能用于类对象,现在也可以用于任意表达式了。 JIT(Just-In-Time)编译器带来了性能和可用性提升。

 

其中一些示例所基于的数据结构需要从此处下载并安装 php_ds 扩展。在 Windows 上,需要从此处下载 8.1 非线程安全(Non-Thread-Safe,NTS)x64 的 DLL,并将 php_ds-1.4.0-8.1-NTS-vs16-x64.zip 文件解压缩到某个目录下。解压后,将目录中的 php_ds.dll 复制到 PHP 8.x 安装根目录中的 .\ext 目录中,例如 C:\PHP-8.1.9-nts-Win32-vs16-x64\ext 。并在 php.ini 配置文件中添加如下行:

 

extension=php_ds

 

如果 PHP 内置服务器已经在运行了,那么请重新启动它。

 

注解(Attributes)

 

注解(Attributes)是配置指令,用于使用结构化的和机器可读的元数据注释或修饰类、方法、函数、参数、属性和类常量。“结构化”意味着可以读取和解析元数据的信息,这使得注解不同于非结构化的文档注释,后者只是普通字符串。注解可用于提供仅在某些时候相关的配置和其他信息,因此它可以是嵌入式的,而不是硬编码到 PHP 脚本中。以下是使用注解的典型顺序:

 

  1. 声明注解类。注解类是一个常规的 PHP 类,其前面是一行单独使用的 #[Attribute] 。该类可以选择性地声明一个构造函数。注解由其类标识;当使用该注解时,类名就是注解名。

  2. 在类、方法、函数、参数、属性和类常量声明上应用或使用该注解。例如,如果注解类名为 Validate ,则该注解用作 #[Validate] 。同一注解可以在不同的声明中多次使用。并且多个注解也可以应用于同一个声明。

  3. 如果需要,可以在运行时中使用反射API获取或读取注解。注解有多种用途,例如:

 

  • 提供接口的替代方案,好处是实现接口的类需要实现接口中的所有方法,而注解可以仅在需要时使用,从而避免不必要的方法实现。

  • 更改编译、诊断、代码生成和运行时行为。

  • 分离 PHP 引擎和扩展。PHP 核心和扩展可以在某些声明上具有某些注解。

  • 在声明中嵌入特定的配置信息。例如,方法上的注解可以指示该方法监听哪些事件。

  • 从文档块迁移到注解中。默认情况下,可以在任何或所有受支持的声明类型上使用注解。但是,注解的​​使用可能仅限于使用选定的位掩码标志 Attribute::TARGET_CLASSAttribute::TARGET_FUNCTIONAttribute::TARGET_METHODAttribute::TARGET_PROPERTYAttribute::TARGET_CLASS_CONSTANTAttribute::TARGET_PARAMETERAttribute::TARGET_ALL 的一种或多种类型的声明上。 Attribute::TARGET_ALL 标志是默认值。使用位掩码标志 Attribute::IS_REPEATABLE ,可以在单个声明中多次使用同一注解。

 

接下来,我们将通过一个示例来探讨注解的使用。假设你想使用一个或多个排序函数对数组进行排序,例如 sort() 用于升序排序, rsort() 用于降序排序, shuffle() 用于随机排序。你可能还需要验证一下输入的数组,以验证它不是空数组,或者它至少有两个元素以使排序相关。排序类型信息和验证信息可以以注解的形式提供。

 

首先,声明用于验证的注解类。

 

#[Attribute]
class Validate {}

 

然后,为排序类型声明另一个注解类。位掩码标志 Attribute::TARGET_CLASSAttribute::IS_REPEATABLE 表示该注解仅用于类声明,并且该注解是可重复的。

 

#[Attribute(Attribute::TARGET_CLASS|Attribute::IS_REPEATABLE)]
class SortType {
function __construct($sortType){
        $this->sortType = $sortType;
    }
}

 

现在声明带有 sortArray() 方法的接口。

 

interface Sort 
{   
    public function sortArray();
}

 

最后,声明 Sort 接口的实现类。

 

class SortImpl implements Sort
{
…}

 

在类中,声明两个类属性,一个用于排序类型,另一个用于要排序的数组。

 

public string $sortType="";
public $arrayToSort=array("B", "A", "f", "C");

 

声明一个方法来验证要排序的数组不是空数组。用 #[Validate] 注解来注解该方法。将注解应用于方法,可以使用反射 API 来发现该方法。

 

#[Validate]
    public function arrayEmpty()
    {
        if (count($this->arrayToSort) === 0) {
            throw new RuntimeException("Array is empty; please provide a non-empty array");
        }
    }

 

声明第二个方法来验证数组是否至少有两个元素,并对其应用 #[Validate] 注解。

 

#[Validate]
    public function arraySize()
    {
        if (sizeof($this->arrayToSort) < 2) {
            throw new RuntimeException("Please provide an array of size 2 or more");
        }
    }

 

实现 sortArray 函数。根据 SortType 的值,进行升序/降序/随机排序。

 

public function sortArray()
    { 
        
         if ($this->sortType == "asc") {
            …
        } elseif ($this->sortType == "desc") {
            …
        } else {
              
             …
    }

 

添加一个名为 performSort(Sort $sort) 的函数来执行排序。在该函数中,在执行排序之前会先应用验证。

 

声明一个类并对其应用 #[SortType] 注解。由于该注解是可重复的,因此多次应用该注解以执行不同类型的排序。

 

#[SortType(sortType: "desc")] 
#[SortType(sortType: "shuffle")]  
#[SortType(sortType: "asc")]              
class QSort
{
}

 

最后,创建一个 SortImpl 类的实例。

 

$sort = new SortImpl();

 

使用反射 API 获取类注解。

 

$ref    =   new ReflectionClass(QSort::class);
$attrs  =   $ref->getAttributes();  

 

遍历注解数组,并调用 performSort() 函数,对每种排序类型执行排序,并输出排序结果。

 

foreach ($attrs as $attr) {
…
}

 

GitHub上提供了演示注解使用的完整 sort.php 脚本。将 sort.php 脚本复制到 scripts 文件夹,并在http://localhost:8000上运行并监听 PHP 内置服务器,使用 url  http://localhost:8000/scripts/sort.php在浏览器中调用 sort.php 脚本。使用不同排序类型的排序结果将显示在浏览器中,如图 1 所示。

 

图 1 使用注解排序的结果

 

因为 shuffle 排序是一种随机排序,所以每次运行脚本时,shuffle 的结果可能不同,如图 2 所示。

 

图 2 Shuffle 排序产生不同的结果

 

因为要排序的示例数组有 4 个元素,所以没有一个验证失败。如果示例数组为空,作为对比,使用相同脚本,仅更改示例数组,将会生成运行时异常消息: Uncaught RuntimeException: Array is empty; please provide a non-empty array 。类似地,如果示例数组只有 1 个元素,也会生成运行时异常,并显示消息: Uncaught RuntimeException: Please provide an array of size 2 or more

 

增强的 new 运算符

 

new 运算符用于创建类的实例。从 PHP 8.1 开始,new 运算符可以用于具有如下语法的任意表达式中,其中表达式必须用圆括号括起来。

 

new (expression)(...$args)

 

new 运算符可用于参数默认值的初始化设置、静态变量的初始化设置、全局常量的初始化设置和注解参数,接下来将通过示例进行探讨。

 

在函数的参数默认值中使用 new 运算符

 

为了演示在函数的参数默认值中使用 new 运算符,我们将使用数组排序示例的变体。考虑一个名为 SortArray 的类,它声明了一个方法 sortArray($arrayToSort) 来按升序对数组进行排序。

 

class SortArray {
    public function sortArray($arrayToSort) {
    …
    }
}

 

第二个类,名为 ReverseSortArray ,它声明了一个按降序排列数组的方法。

 

class ReverseSortArray {
    public function sortArray($arrayToSort) {
…
}
}

 

现在声明一个可接受任意数组的函数以及一个数组排序器来对数组进行排序。可以使用 new 运算符来定义默认的数组排序器,以创建 SortArray 的实例。

 

function sortAnyArray($arrayToSort, $arraySorter = new SortArray)
{
    return $arraySorter->sortArray($arrayToSort);
}

 

完整的脚本可以在 GitHub上找到。使用 url http://localhost:8000/scripts/sample.php在内置服务器上运行示例脚本。

 

输出如下所示:

 

arrayToSort[0] = A arrayToSort[1] = B arrayToSort[2] = C arrayToSort[3] = f
arrayToSort[0] = f arrayToSort[1] = C arrayToSort[2] = B arrayToSort[3] = A

 

在变量和常量初始化中使用 new 运算符

 

new 运算符可用于静态变量的初始化设置和全局常量的初始化设置。但是,在静态和非静态类属性的初始化设置中,以及在类常量的初始化设置中,都不支持 new 运算符。下面的脚本演示了哪些变量和常量的初始化设置是受支持的,哪些是不受支持的。

 

<?php
class A{ }
class B{
//static $a = new A; //在此上下文中不支持new表达式
//public $a = new A; //在此上下文中不支持new表达式
//const C = new A; //在此上下文中不支持new表达式
}
static $a = new A;
 
const C = new A;

 

在注解参数中使用 new 运算符

 

正如你所料, new 运算符也可以用于注解参数。我们将使用与演示注解功能相同的示例,但有一点不同。请记住,我们使用 #[SortType] 注解来注解 QSort 类,并将注解参数作为字符串传递给它。

 

#[SortType(sortType: "desc")] 
#[SortType(sortType: "shuffle")]  
#[SortType(sortType: "asc")]              
class QSort
{
}

 

针对这种情况,声明一个名为 Str 的类,它接受一个字符串型的参数作为构造函数的参数。

 

class Str{
    function __construct($str){
        $this->value = $str;
    }
    function __toString(){
        return $this->value;
    }
     
}

 

在注解参数中使用 new 运算符,脚本如下所示。

 

#[SortType(sortType: new Str("desc"))] 
#[SortType(sortType: new Str("shuffle"))]  
#[SortType(sortType: new Str("asc"))]              
class QSort
{
}

 

在内置服务器中运行脚本,输出与之前相同,如图 3 所示。

 

图 3 在注解参数中使用 new 的结果

 

在某些上下文中不允许使用 new 运算符

 

前面我们提到了一些不支持 new 运算符的上下文,即静态和非静态类属性的初始化设置,以及类常量的初始化设置。 此外,在以下的上下文中也不支持 new 运算符:

 

  • 非字符串动态类名

  • 常量表达式中的参数解包(Argument unpacking)

  • 常量表达式中的匿名类

  • 不受支持的常量表达式下面脚本中所注释掉的语句如果运行都会生成错误:

 

<?php
 $list = [4, 6];
function fn1(
    $a1 = new ('some_dynamic_class')(),
   //  $a2 = new (some_dynamic_class)(), // 非字符串动态类名 -  不能在常量表达式中使用动态类名
   //  $b = new  class {}, // 不能在常量表达式中使用匿名类
   // $c = new C(...$list), // 不支持在常量表达式中解包参数
   //   $d = new D($x), // 常量表达式包含无效操作
) {}

 

新的 match 表达式

 

PHP 8 引入了一个新的 match 表达式作为控制流结构,它使用身份比较(identity comparison)将给定的主题表达式与一个或多个可选分支相匹配,并返回匹配分支的结果值。match 表达式类似于 switch 语句,但区别如下:

 

  • match 表达式使用身份运算符( === )进行比较,而switch 则使用相等运算符( == )。

  • match 表达式返回一个值,而switch 不返回。返回值可以赋值给某个变量。

  • 在匹配其中某个分支后,match 表达式会自动终止matchswitch 的终止则需要使用 break; 声明。

  • match 表达式不会像switch那样在没有声明break; 的情况下失败。

  • match 表达式支持在同一个分支中用逗号 (,)分隔多个条件,而switch 不支持。

  • match 表达式必须是穷尽的,这意味着它必须要处理主题表达式的所有值。接下来,我们通过一些示例来演示 match 表达式的使用。

 

输出返回值

 

先从一个简单的示例开始,下面脚本中的match 表达式将作为主题表达式的整数 1 与包含默认模式的多个条件表达式进行匹配。

 

<?php
echo match (1) {
    0 => 'A',
    1 => 'B',
    2 => 'C',
    default => 'Default',
};

 

在内置引擎中运行脚本,通过使用 url http://localhost:8000/scripts/match.php在浏览器中调用脚本,输出为:

 

B

 

在下面的脚本中,主题表达式与任何非默认条件表达式都不匹配。

 

<?php
echo match (3) {
    0 => 'A',
    1 => 'B',
    2 => 'C',
    default => 'Default',
};

 

输出为:

 

Default

 

default 条件不能与脚本中所示的其他条件组合:

 

<?php
echo match (3) {
    0 => 'A',
    1 => 'B',
    2, default => 'C',
     
};

 

当脚本运行时,它会生成如下错误:

 

Parse error: syntax error, unexpected token "default", expecting "=>"

 

一个相对复杂的示例,下面的脚本有一个通过 \Ds\Vector 类初始化的变量。主题表达式以数组表示法访问 vector 变量。

 

<?php
$vector = new \Ds\Vector();
$vector->push('a');
$vector->push('b', 'c');
$vector[] = 'd';
 
echo match ($vector[1]) {
  'a' => "a",
  'b' => "b",
};

 

使用 url http://localhost:8000/scripts/match.php 在浏览器中调用脚本。

 

结果是:

 

b

 

将返回值赋给变量

 

返回值也可以赋值给某个变量,匹配条件可以是任意表达式。在下面的脚本中, match 表达式将布尔值 true 作为主题表达式与调用其他内置字符串函数的条件表达式进行匹配。

 

<?php
$text = 'A B C D';
$result = match (true) {
    str_word_count($text)==3 || str_word_count($text)==2 => '2 or 3',
    str_word_count($text)==4 || str_word_count($text)==5 => '4 or 5',
    
};
var_dump($result);

 

输出是:

 

string(6) "4 or 5"

 

没有强制类型转换

 

switch 语句进行松散比较不同, match 表达式进行严格比较,并且不进行类型强制转换。首先,看一个与 switch 语句进行松散比较的示例。下面的脚本会匹配第一个条件并输出值 a

 

<?php
$var = 0;
switch((string)$var) 
{
    case  0  : echo 'a'; break; // 这将测试NULL或空字符串   
    default : echo 'b'; break; // 其他的,包括零
}

 

相比之下,下面脚本中的match 进行了严格比较,并匹配默认条件输出 Default

 

<?php
$var = 0;
 
echo match ((string)$var) {
    0 => 'a',
    default => 'Default',
};

 

即使是 NULL 值也可使用身份比较进行匹配

 

match 表达式使用身份运算符( === )进行身份比较,这意味着即使是 NULL 值也会匹配。由于 Ds\Vector::push() 方法返回 NULL ,所以,下面的脚本中的match 表达式实际上与Ds\Vector::push() 方法所返回的NULL 值相匹配。

 

<?php
$vector = new \Ds\Vector();
$vector->push('a'); 
 
match ($vector->push('b')) {
  $vector->push('d') => $vector->push('b'),
  $vector->pop() => $vector->pop(),
};
print_r($vector);

 

匹配的是第一个条件,输出为:

 

Ds\Vector Object ( [0] => a [1] => b [2] => d [3] => b )

 

身份比较( === )可以应用于任意值,即使没有返回值也是如此。 match 返回的 NULL 值可以赋值给某个变量。 下面的脚本匹配 NULL 值,并且匹配的是第一个条件。

 

<?php
$vector = new \Ds\Vector();
$vector->push('a'); 
 
$vector = match ($vector->push('b')) {
  $vector->push('c') => $vector->push('b'),
  $vector->pop() => $vector->pop(),
};
var_dump($vector);

 

使用 url http://localhost:8000/scripts/sample.php 调用脚本,输出为:

 

NULL

 

可以用逗号分隔多个条件

 

多个条件表达式可以用逗号分隔。首先看一个具有单个条件的示例,脚本如下所示。

 

<?php
$vector = new \Ds\Vector();
$vector->push('a'); 
 
$vector = match ($vector->push('b')) {
  $vector->push('c') => $vector->push('b'),
  $vector->pop() => $vector->pop(),
};
var_dump($vector);

 

输出是:

 

Ds\Vector Object ( [0] => a [1] => b )

 

以相同的示例为例,添加多个用逗号分隔的条件。

 

<?php
$vector = new \Ds\Vector();
$vector->push('a'); 
 
match ('puush') {
  'push','puush' => $vector->push('b') 
   
};
print_r($vector);

 

第一个条件表达式列表中所列出的第二个可选选项将与拼写错误的“puush”相匹配,输出为:

 

Ds\Vector Object ( [0] => a [1] => b )

 

穷举

 

match 表达式必须是可穷举的。在下面的脚本中,主题表达式与任何条件都不匹配。

 

<?php

$vector = new \Ds\Vector();
$vector->push('a'); 
 
match ('set') {
  'push' => $vector->push('b'),
  'pop' => $vector->pop(),
};
print_r($vector);

 

结果是输出一个错误:

 

Uncaught UnhandledMatchError: Unhandled match case 'set'

 

instanceof 运算符支持任意表达式

 

另一个新特性是 instanceof 运算符可接受任意表达式。仅有的两个要求是:

 

  • 表达式必须用圆括号括起来,并且

  • 表达式的计算结果必须为字符串。为了演示示新的instanceof 运算符,请创建一个 PHP 脚本 sample.php。声明三个不同类型的集合变量,类型如 \Ds\Vector\Ds\Set

 

$collection_a = new \Ds\Vector([1, 2, 3]);
$collection_b = new \Ds\Vector();
$collection_c = new \Ds\Set();

 

添加一个返回类的任意函数,如返回字符串形式的 \Ds\Vector::class

 

function getSomeClass(): string
{
    return \Ds\Vector::class;
}

 

输出对不同集合变量使用 instanceof 运算符后的结果。instanceof 运算符接受一个表达式,该表达式在每次调用中会被计算为字符串。sample.php 的脚本如下所示。

 

<?php
$collection_a = new \Ds\Vector([1, 2, 3]);
$collection_b = new \Ds\Vector();
$collection_c = new \Ds\Set();
function getSomeClass(): string
{
    return \Ds\Vector::class;
}
var_dump($collection_a instanceof ('\Ds'.'\Vector'));
var_dump($collection_a instanceof ('\Ds'.'\Hashtable'));
var_dump($collection_b instanceof ('\Ds'.'\Vector'));
var_dump($collection_b instanceof ('\Ds'.'\Set'));
var_dump($collection_c instanceof ('\Ds'.'\Set'));
var_dump(new \Ds\Vector instanceof (getSomeClass()));
var_dump($collection_a instanceof ('\Ds'.'\Set'));

 

该脚本在 PHP 8 中能正常运行,并生成如下的输出:

 

bool(true) 
bool(false)
bool(true) 
bool(false) 
bool(true) 
bool(true) 
bool(false) 

 

JIT(Just-In-Time)编译器

 

PHP 8 引入了即时(Just-in-time ,JIT)编译,其目的如下:

 

  • 提高性能和可用性。

  • 使 PHP 适用于非 Web 的 CPU 密集型用例,在这些用例中可以获得显著的性能优势。

  • 创造使用 PHP 而不是 C 开发内置函数的潜力。PHP 可能是一个更好的选择,因为它没有 C 语言那样的内存管理和溢出问题。引入了两个 JIT 编译引擎:跟踪 JIT(Tracing JIT)和函数 JIT(Function JIT)。函数 JIT 只优化单个函数范围内的代码,而跟踪 JIT 则能优化整个堆栈跟踪。在综合基准测试中,跟踪 JIT 可以提高 3 倍的性能,在长时间运行的查询上可提高 1.5 到 2 倍。在 Mandelbrot 基准测试中,跟踪 JIT 可以将性能提高 4 倍以上。

 

在本文中,我们介绍了一些与 PHP 8 语法最相关的改进,包括注解(attributes)、 new 运算符、 match 表达式等。在下一篇文章中,我们将探讨类和构造函数的改进。

 

作者介绍:Deepak Vohra 是 Oracle 认证的 Java 程序员和 Oracle 认证的 Web Component 开发人员。Deepak 已经出版了 20 多本书。

 

原文链接:https://www.infoq.com/articles/php8-attributes-match-new-operator/

季度最有价值文章

月度最有价值文章

帖子投票

名称 是否有价值
BigLoser

投票统计

是否原创: 100 %

100 % Complete (success)

是否有价值: 100 %

100% Complete

是否有素质: 100 %

100% Complete (warning)

是否合法: 100 %

100% Complete

   群组工具