PHP 8.3:最新版本中的新功能和变化

PHP 8.3:最新版本中的新功能和变化

PHP 8.3 于 11 月 23 日如期发布,它包含了自 PHP 8.2 发布以来的许多新功能和改进。尽管官方认为这只是一个次要版本,但 8.3 中的一些变化可能会直接影响到你的 PHP 工作-也许能帮助你更快地编写代码,减少 bug。

让我们深入了解一下这个最新版本所带来的重大变化(有时并不那么重大)。

PHP 8.3 的新功能和改进

让我们先来看看 PHP 8.3 中最引人注目的功能。

  1. 类型类常量
  2. 新的 json_validate() 函数
  3. 深度克隆 readonly 属性
  4. 新的 #[\Override] 属性
  5. 动态获取类常量和枚举成员
  6. 新的 getBytesFromString() 方法
  7. 新的 getFloat() 和 nextFloat() 方法

类型类常量

从 PHP 7.4 开始,我们就可以为类属性声明类型了。然而,尽管多年来对 PHP 类型进行了多次调整,但直到现在才扩展到常量。

在 PHP 8.3 中,类常量(也包括接口、特质和枚举常量)可以进行类型化,这样开发人员就不太可能偏离常量最初声明的意图了。

下面是一个使用接口的基本示例:

// Legal:
interface ConstTest {
// Declared type and value are both strings
const string VERSION = "PHP 8.3";
}
// Illegal:
interface ConstTest {
// Type and value mismatch in this initial declaration
const float VERSION = "PHP 8.3";
}

这些类型化类常量的真正价值是在处理从基本声明派生出来的类时显现出来的。虽然子类可以经常为常量赋新值,但 PHP 8.3 可以帮助防止意外改变常量的类型,使其与初始声明不兼容:

class ConstTest {
const string VERSION = "PHP 8.2";
}
class MyConstTest extends ConstTest {
// Legal:
// It's OK to change the value of VERSION here
const string VERSION = "PHP 8.3";
// Illegal:
// Type must be declared if it was specified in the base class
const VERSION = "PHP 8.3";
// Illegal:
// In this case, we can't change the type declared in the 
// base class, even if the new type and its value are compatible.
const float VERSION = 8.3;
}

请记住,在 “narrowing” 多个类型或使用其他兼容类型时,分配给类常量的类型可能会不同:

class ConstTest {
const string|float VERSION = "PHP 8.2";
}
class MyConstTest extends ConstTest {
// Legal:
// Here, it's OK to narrow the type declaration to string or float
const string VERSION = "PHP 8.3";
const float VERSION = 8.3;
// Legal:
// Value could be an int, but it's compatible with float 
const float VERSION = 8;
// Illegal:
// We can't widen the type options here to include int
const string|float|int VERSION = 8;
}

在验证返回值时,其他属性支持的两种类型  – voidnever – 不支持作为类常量类型。

新的 json_validate() 函数

在处理 JSON 编码数据时,很高兴能在尝试对其进行处理之前知道有效载荷在语法上是否有效。

在以前的 PHP 版本中,开发人员使用 json_decode() 函数,并在该函数尝试将 JSON 数据转换为关联数组或对象时检查错误。PHP 8.3 的新 json_validate() 函数可以在不使用构建数组或对象结构所需的全部内存的情况下进行错误检查。

因此,在过去,您可能会像这样验证 JSON 有效负载:

$obj = json_decode($maybeJSON);
if (json_last_error() === JSON_ERROR_NONE) {
// Probably do something with $obj   
}

如果您不打算立即对上例中的 $obj 执行操作,那么仅仅确认原始 JSON 有效负载的有效性就会占用大量资源。在 PHP 8.3 中,您可以这样做来节省一些内存:

if (json_validate($maybeJSON) {
// Do something with $maybeJSON   
}

注:使用 json_validate() ,然后立即通过 json_decode() 运行数据,无论如何都会占用 decode 的内存资源,这样做意义不大。您更有可能使用新函数来验证 JSON,然后再将其存储到某个地方或作为请求响应交付。

深度克隆 readonly 属性

将单个类属性声明为 readonly 的功能出现在 PHP 8.1 中。PHP 8.2 引入了将该属性分配给整个类的功能。然而,许多开发人员认为,在使用包含此类属性的类时受到的限制妨碍了有用的编程。

This code example from the RFC shows how it works:

一份关于修改 readonly 行为的 RFC 提出了两项建议:

  1. 允许非只读类扩展 readonly
  2. 允许在克隆时重新初始化 readonly 属性

第二项建议已被引入 PHP 8.3。新方法允许在 __clone 魔术方法中(包括通过在 __clone 中调用的函数)重新初始化具有 readonly 属性的类实例。

RFC 中的代码示例展示了它是如何工作的:

class Foo {
public function __construct(
public readonly DateTime $bar,
public readonly DateTime $baz
) {}
public function __clone() {
// $bar will get a new DateTime when clone is invoked
$this->bar = clone $this->bar; 
// And this function will be called
$this->cloneBaz();
}
private function cloneBaz() {
// This is legal when called from within __clone
unset($this->baz); 
}
}
$foo = new Foo(new DateTime(), new DateTime());
$foo2 = clone $foo;

新的 #[\Override] 属性

在 PHP 中实现接口时,程序员会为这些接口中命名的方法提供详细的功能。在创建类的实例时,程序员可以通过在子类中创建具有相同名称和兼容签名的替代版本来覆盖父类方法。

一个问题是,程序员可能会认为他们是在实现接口方法或覆盖父方法,其实不然。由于子类方法的名称有错别字,或者由于父类代码中的方法被删除或重命名,他们可能会创建一个完全独立的怪兽。

PHP 8.3 引入了 #[\Override] 属性,以帮助程序员明确方法必须在代码中具有一定的沿袭关系。

下面是一个基本例子:

class A {
protected function ovrTest(): void {}
}
// This will work because ovrTest() 
// can be found in the parent class
class B extends A {
#[\Override]
public function ovrTest(): void {}
}
// This will fail because ovrBest() 
// (probably a typo) is not in the parent
class C extends A {
#[\Override]
public function ovrBest(): void {}
}

动态获取类常量和枚举成员

与 PHP 代码中的其他属性不同,使用变量名获取类常量和枚举成员有点复杂。在 PHP 8.3 之前,您可能会使用 constant() 函数来获取常量,如下所示:

class MyClass {
public const THE_CONST = 9;
}
enum MyEnum int {
case FirstMember = 9;
case SecondMember = 9;
}
$constantName = 'THE_CONST';
$memberName = 'FirstMember';
echo constant('MyClass::' . $constantName);
echo constant('MyEnum::' . $memberName)->value;

现在,使用上面相同的类和枚举定义,就可以通过 PHP 8.3 的动态常量获取功能实现相同的结果:

$constantName = 'THE_CONST';
$memberName = 'FirstMember';
echo MyClass::{$constantName};
echo MyEnum::{$memberName}->value;

新的 getBytesFromString() 方法

您是否曾想过使用预先批准的字符集生成随机字符串?现在有了 getBytesFromString() 方法,您就可以轻松实现这个愿望了,该方法已添加到 PHP 8.3 的随机扩展中。

这个新方法很简单:将字符串作为源材料传给它,并指定要使用的字符个数。然后,该方法将从字符串中随机选择字节,直到达到指定长度为止。

下面是一个简单的示例:

$rando = new Random\Randomizer();
$alpha = 'ABCDEFGHJKMNPQRSTVWXYZ';
$rando->getBytesFromString($alpha, 6); //  "MBXGWL"
$rando->getBytesFromString($alpha, 6); //  "LESPMG"
$rando->getBytesFromString($alpha, 6); //  "NVHWXC"

随机输出所要求的长度有可能比输入字符串的字节数还多:

$rando = new Random\Randomizer();
$nums = '123456';
$rando->getBytesFromString($nums, 10); //  "2526341615"

输入字符串中的每个字符都是独一无二的,每个字符被随机结果选中的几率相等。不过,可以通过让输入字符比其他字符出现得更频繁来对它们进行加权。例如

$rando = new Random\Randomizer();
$weighted = 'AAAAA12345';
$rando->getBytesFromString($weighted, 5); //  "1AA53"
$rando->getBytesFromString($weighted, 10); //  "42A5A1AA3A"

新的 getFloat()nextFloat() 方法

在扩展随机扩展的同时,PHP 8.3 引入了两个生成随机浮点数值的新方法: getFloat()nextFloat()

下面是一个例子:

$rando = new Random\Randomizer();
// Generate a float value between a minimum 
//  value of 0 and a maximum value of 5
$rando->getFloat(0,5); // 2.3937446906217

getFloat() 方法在最小值和最大值之后还接受第三个参数。使用 Random\IntervalBoundary 枚举可以确定函数是否可以返回最小值和最大值。

以下是规则:

  • IntervalBoundary::ClosedOpen: 可以返回最小值,但不能返回最大值
  • IntervalBoundary::ClosedClosed: 可以返回最小值和最大值
  • IntervalBoundary::OpenClosed: 不能返回最小值,可以返回最大值
  • IntervalBoundary::OpenOpen: 既不返回最小值,也不返回最大值

使用 getFloat() 而不指定 Enum 作为第三个参数时,默认值是 IntervalBoundary::ClosedOpen

新函数的文档中提供了一个有用的示例,可以随机生成经纬度坐标,其中纬度可以包括 -90 和 90,但经度不能包括 -180 和 180(因为它们是相同的):

$rando = new Random\Randomizer();
printf(
"Lat: %+.6f Long: %+.6f",
$rando->getFloat(-90, 90, \Random\IntervalBoundary::ClosedClosed),
// -180 will not be used 
$rando->getFloat(-180, 180, \Random\IntervalBoundary::OpenClosed),
);

新的 nextFloat() 方法与使用 getFloat() 方法请求范围从 0 到小于 1 的随机值基本相同:

$rando = new Random\Randomizer();
$rando->nextFloat(); // 0.3767414902847

PHP 8.3 中的其他小改动

PHP 8.3 还包括许多其他新功能和小改动。我们将在下文中提及它们,并提供其他资源的链接(如有):

PHP 8.3 中的弃用

在 PHP 的每个新版本中,都会有一些函数和设置被标记为最终将被删除。这些功能一旦被弃用,就不建议继续使用,当它们出现在执行代码中时,会在许多日志中生成通知。

下面是 PHP 8.3 中的弃用功能列表,并附有其他信息的链接:

  • U_MULTIPLE_DECIMAL_SEPERATORS 常量已被弃用,改为 U_MULTIPLE_DECIMAL_SEPARATORS。
  •  3MT_RAND_PHP Mt19937 变体已被弃用。
  •  ReflectionClass::getStaticProperties() 不再为空。
  •  assert.activeassert.bailassert.callbackassert.exception, 和 assert.warning 等 INI 设置已被弃用。
  • 不带参数调用 get_class()get_parent_class() 已被弃用。

小结

我们查看了 PHP 8.3 中的重大变化。要查看该版本中每项更新的详细列表,请查阅该版本的官方更新日志。如果您计划将代码转移到运行最新 PHP 的平台上,《8.2 到 8.3 迁移指南》可能会帮您避免麻烦。

如果你负责在开发或生产服务器上安装 PHP,8.3 现在就可以下载了。

评论留言