优秀的编程知识分享平台

网站首页 > 技术文章 正文

PHP 8.3:最新版本中新增和更改的内容

nanyue 2024-11-11 12:27:10 技术文章 1 ℃

PHP 8.3于11月23日如期发布,自PHP 8.2发布以来,它包含了许多新特性和改进。尽管它被官方认为是一个小版本,但是8.3中的一些变化可能会直接影响您使用PHP的工作—可能会帮助您更快地编写代码并减少错误。

让我们看看这个最新版本带来的重大(有时不那么重大)变化。

PHP 8.3的新特性和改进

让我们先来研究一下最受关注的PHP 8.3特性。

  • 类型化类常量
  • 新增json_validate()函数
  • 只读属性的深度克隆
  • 新增#[\Override]属性
  • 动态获取类常量和枚举成员
  • 新增getBytesFromString()方法
  • 新增getFloat()和nextFloat()方法

类型化类常量

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

类常量(也包括接口、trait和枚举常量)可以在PHP 8.3中键入,从而使开发人员不太可能偏离常量初始声明背后的意图。

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

// 合法:
interface ConstTest {
    // 声明的类型和值都是字符串
    const string VERSION = "PHP 8.3";
}

// 非法:
interface ConstTest {
    // 初始声明中的类型和值不匹配
    const float VERSION = "PHP 8.3";
}

当在基声明派生的类中工作时,将显示这些类型化类常量的实际值。虽然子类可以频繁地为常量赋新值,但PHP 8.3可以帮助防止意外地更改其类型,从而使其与初始声明不兼容:

class ConstTest {
    const string VERSION = "PHP 8.2";
}

class MyConstTest extends ConstTest {

    // 合法:
    // 在这里改变VERSION的值是可以的
    const string VERSION = "PHP 8.3";

    // 非法:
    // 类型如果在基类中指定,则必须声明
    const VERSION = "PHP 8.3";

    // 非法:
    // 在这种情况下,不能更改基类中声明的类型,即使新类型和它的值是兼容的。
    const float VERSION = 8.3;
}

请记住,当“缩小”多个类型或使用其他兼容类型时,分配给类常量的类型可能会变化:

class ConstTest {
    const string|float VERSION = "PHP 8.2";
}

class MyConstTest extends ConstTest {

    // 合法:
    // 在这里,可以将类型声明缩小到string或float
    const string VERSION = "PHP 8.3";
    const float VERSION = 8.3;

    // 合法:
    // Value可以是int型,但它与float兼容
    const float VERSION = 8;

    // 非法:
    // 我们不能扩大这里的类型选项以包括int
    const string|float|int VERSION = 8;
}

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

新增json_validate()函数

在处理json编码的数据时,最好先知道有效负载在语法上是否有效,然后再对其进行操作。

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

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

$obj = json_decode($maybeJSON);

if (json_last_error() === JSON_ERROR_NONE) {
    // 可能对$obj做点什么 
}

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

if (json_validate($maybeJSON)) {
    // 对$maybeJSON做些什么
}

注意:使用json_validate(),然后立即通过json_decode()运行数据,使用decode的内存资源是没有多大意义的。在将JSON存储到某个地方或将其作为请求响应交付之前,更有可能使用新函数来验证JSON。

只读属性的深度克隆

在PHP 8.1中出现了将单个类属性声明为只读的功能。PHP 8.2引入了将该属性分配给整个类的功能。然而,许多开发人员认为,在使用包含这些属性的类时所施加的约束妨碍了有用的编程。

一个修改只读行为的RFC提出了两个建议:

允许非只读的类扩展只读的类

允许在克隆时重新初始化只读属性

这是进入PHP 8.3的第二个提案。新方法允许在__clone魔术方法中重新初始化具有只读属性的类的实例(包括通过从__clone中调用的函数)。

这个来自RFC的代码示例展示了它是如何工作的:

class Foo {
    public function __construct(
        public readonly DateTime $bar,
        public readonly DateTime $baz
    ) {}
 
    public function __clone() {
        // 当clone被调用时,$bar将获得一个新的DateTime
        $this->bar = clone $this->bar; 

        // 这个函数会被调用
        $this->cloneBaz();
    }
 
    private function cloneBaz() {
       // 这在__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 {}
}

// 这是可行的,因为可以在父类中找到ovrTest()
class B extends A {
    #[\Override]
    public function ovrTest(): void {}
}

// 这将失败,因为ovrBest()不在父类中
class C extends A {
    #[\Override]
    public function ovrBest(): void {}
}

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

与PHP代码中的其他属性不同,获取具有变量名的类常量和Enum成员有点复杂。在PHP 8.3之前,你可以使用constant()函数这样做:

class MyClass {
    public const THE_CONST = 9;
}

enum MyEnum: int {
    case FirstMember = 9;
    case SecondMember = 10;
}

$constantName = 'THE_CONST';
$memberName = 'FirstMember';

echo constant('MyClass::' . $constantName);
echo constant('MyEnum::' . $memberName)->value;

现在,使用上面相同的类和Enum定义,你可以通过PHP 8.3的动态获取常量实现相同的结果,如下所示:

$constantName = 'THE_CONST';
$memberName = 'FirstMember';

echo MyClass::{$constantName};
echo MyEnum::{$memberName}->value;

新增getBytesFromString()方法

您是否曾经想要使用预先批准的字符集合生成随机字符串?现在,您可以使用getBytesFromString()方法轻松地做到这一点,该方法已在PHP 8.3中添加到Random扩展中。

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

这里有一个简单的例子:

$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还对Random扩展进行了扩展,引入了两个新方法来生成随机浮点值:getFloat()和nextFloat()。

这里有一个例子:

$rando = new Random\Randomizer();

// 生成一个介于最小值0和最大值5之间的浮点值
$rando->getFloat(0,5); // 2.3937446906217

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

以下是规则:

IntervalBoundary::ClosedOpen:可以返回最小值,但不能返回最大值

IntervalBoundary::ClosedClosed:可以同时返回最小值和最大值

IntervalBoundary:: openclose:最小可能不返回,最大可能返回

IntervalBoundary::OpenOpen:最大值和最小值都不能返回

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

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

$rando = new Random\Randomizer();

printf(
    "Lat: %+.6f Long: %+.6f",
    $rando->getFloat(-90, 90, \Random\IntervalBoundary::ClosedClosed),

    // -180不会被使用
    $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还包括许多其他的新函数和一些小的变化。

  • DOMElement类的新方法:DOMElement::getAttributeNames()、DOMElement::insertAdjacentElement()、DOMElement::insertAdjacentText()、DOMElement::toggleAttribute()、DOMNode::contains()、DOMNode::getRootNode()、DOMNode::isEqualNode()、DOMNameSpaceNode::contains()和MParentNode::replaceChildren()。
  • IntlCalendar类的新方法:IntlCalendar::setDate()、IntlCalendar::setDateTime()、IntlGregorianCalendar::createFromDate()和IntlGregorianCalendar::createFromDateTime()。
  • 新增LDAP函数:ldap_connect_wallet()和ldap_exop_sync()。
  • 新增mb_str_pad()多字节字符串函数。
  • 新增POSIX函数:posix_sysconf(), posix_pathconf(), posix_fpathconf()和posix_eaccess()。
  • 新增了createFromMethodName()方法。
  • 新增套接字函数:socket_atmark()。
  • 新增字符串函数:str_increment()、str_dec递减()和stream_context_set_options()。
  • 新的ZipArchive类方法:ZipArchive::getArchiveFlag()。
  • 新的INI设置来设置允许的最大堆栈大小:zend.max_allowed_stack_size。

PHP 8.3中的弃用

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

以下是PHP 8.3的弃用列表,并附有附加信息的链接:

不赞成使用U_MULTIPLE_DECIMAL_SEPARATORS常量,而改用U_MULTIPLE_DECIMAL_SEPARATORS。

3MT_RAND_PHP Mt19937变体已弃用。

ReflectionClass::getStaticProperties()不再为空。

INI设置断言。活跃,断言。保释,断言。回调,断言。异常和断言。已弃用警告。

不带参数调用get_class()和get_parent_class()已被弃用。

最近发表
标签列表