2023 年底,PHP 8.3.0 版本发布 (🐕 也为了冲业绩

2023 年底,PHP 8.3.0 版本发布 (:dog: 也为了冲业绩

PHP 8.3

  • Title: 2023 年底,PHP 8.3.0 版本发布
  • Tag: PHP
  • Author: Tacks
  • Create-Date: 2023-11-25
  • Update-Date: 2023-11-25

Ref

Hello PHP 8.3.0

在2023年11月23日, 恭喜 PHP 8.3.0 新的大版本重磅发布 !

PHP8.3 ,新增 JSON有效性检查函数类常量显式类型只读属性深拷贝,当然还有一些性能优化、错误修复,常规清理等。

它对只读类、新函数、最近添加的类的补充Randomizer、堆栈溢出检测等进行了改进。

捋一下 PHP 更新节奏

从 php8.0.0 从 2020 发布开始,基本上每年更新一个版本,直到现在的 8.3 ,希望保持这个节奏啊,如果可以预计 2025 能看到 php9 ?

  • 📌 26 Nov 2020 🐘 php8.0.0
  • 📌 23 Nov 2021 💰 THE PHP Foundation PHP 基金会成立
  • 📌 25 Nov 2021 🐘 php8.1.0
  • 📌 08 Dec 2022 🐘 php8.2.0
  • 📌 23 Nov 2023 🐘 php8.3.0

从时间是来看,今年比以往来得更早一些,提前完成 KPI,加油 PHPer 💞

个人对本次php8.3更新的一些小理解

  • 💥💥💥💥💥 日常使用方法最相关
  • ⭐⭐⭐⭐ 类型声明之路逐步完善
  • ☔☔☔ 特定情况下解决方案
  • 💧💧 不痛不痒的更新
  • ⚡ 比较意外但貌似帮助也不大
  • ⛔ 打破了你之前的认识

新的功能

0x1. json_validate() Function (json校验函数) 💥💥💥💥💥

过去&现在

  • before

验证字符串是否为有效 JSON 的唯一方法是对其进行 json_decode() 解码并检测是否抛出任何错误;

  • now

千呼万唤始出来, json_validate() 函数来了,它只判断输入是否是有效的 JSON,相比 json_decode() ,使用内存更少;

用法

json_validate(string $json, int $depth = 512, int $flags = 0): bool

/**
 * json 验证 (php83之前)
 *
 * @param string $string
 * @return boolean|string
 */
function jsonValidate(string $string): bool|string {
    $data = json_decode($string);
    if($data === null || json_last_error() !== JSON_ERROR_NONE) {
        return json_last_error_msg();
    } 
    return true;
}


// php8.3
json_validate('{ "test": { "foo": "bar" } }'); // true
json_validate('{ "test": { "foo": "bar" , "test"} }'); // false

影响范围

说真的,这并不会影响我们平时的开发,因为,99%的场景下我们本身就是需要用到 json 解码后的数据来做操作,而不只是单纯的验证 json 字符串。

so,只有一些小部分场景可能需要,如你只需要判断用户的 json 内容然后录入系统中,通常如 日志类json 等。

0x2. Typed class constants (类常量显式类型) ⭐⭐⭐⭐

过去&现在

  • before

从 php8,就逐步布局类型定义,虽然背后还是弱类型,但提供了类型声明,就能让编译器/解析器,提前告诉你代码可能的问题,但类常量还没有支持,这不,一步步完善

  • now

php8.3 ,增加了类常量的类型定义,如果你给了不符合类型的值,将会抛出 Fatal error 致命错误,当然这不是必须的,你依然可以不参用类型声明

用法

class BeforeFoo
{
    // php83 之前,可以直接这么定义
    const BAR = 'bar'; 
}

class Foo
{
    // php83 时,可以给常量也加上类型声明
    const string BAR = 'bar'; 
}

class ErrFoo
{
    // php83 时,你可以给常量加类型声明,但如果错误,将会抛出异常
    // Fatal error: Cannot use string as value for class constant ErrFoo::BAR of type int
    const int BAR = 'bar'; 
}

0x3. Dynamic class constant fetch (动态类常量获取) 💧💧

过去&现在

  • before

php 貌似很喜欢玩动态类型获取。比如 $$var 可变变量,放眼其他编程语言貌似都没这么玩的 ,顺便看看 到底有哪些动态获取成员名称的手段

// 可变变量
$$foo;
// 静态属性
Foo::${$bar};
// 静态属性
$obj::$bar;
// 成员属性
$obj->$foo;
// 静态方法
Foo::{$bar}();
// 静态方法
$obj::bar();
// 方法
$foo->{$bar}();
  • now

看到 类常量动态获取的方式 还没引入,php83 那就引入吧

// 类常量
Foo::{$name};

用法

class Foo 
{
    const BAR = 'bar';
    public $name = '1';
}
$name = 'BAR';

// php8.3 之前
echo constant(Foo::class . '::' . $name) , PHP_EOL;

// php8.3 现在
echo Foo::{$name} , PHP_EOL;

// php8.3 现在 切记给变量带上 {} ,它标识先解析 name 变量,再获取类常量
// 下面是错误用法:Fatal error: Uncaught Error: Access to undeclared static property Foo::$name
// echo Foo::$name;

更多

class MyClass {
    const VAR1 = 'class constants';
    public static $var2 = 'class static property';
    public $var3 = 'class property';

    public static function func1() { return 'class static function '. __FUNCTION__; }
    public function func2() { return 'class function '. __FUNCTION__; }

}

echo '【类常量-类-直接获取】            |   MyClass::VAR1           | ',  MyClass::VAR1 , PHP_EOL;
echo '【类静态属性-类-直接获取】        |   MyClass::$var2           | ',  MyClass::$var2 , PHP_EOL;
echo '【成员属性-对象-直接获取】        |   (new MyClass())->var3    | ',  (new MyClass())->var3 , PHP_EOL;

echo "================================================================================================", PHP_EOL;

$bar = 'VAR1';
echo '【类常量-类-动态获取】        | bar = VAR1 | MyClass::{$bar};         | ', MyClass::{$bar} , PHP_EOL;

$bar = 'var2';
echo '【类静态属性-类-动态获取】    | bar = var2 | MyClass::${$bar};         | ', MyClass::${$bar} , PHP_EOL;

$bar = 'var2';
echo '【类静态属性-对象-动态获取】  | bar = var2 | (new MyClass)::${$bar};   | ', (new MyClass)::${$bar} , PHP_EOL;

$bar = 'var3';
echo '【成员属性-对象-动态获取】    | bar = var3 | (new MyClass)->${$bar};   | ', (new MyClass)->{$bar} , PHP_EOL;

$bar = 'func1';
echo '【静态方法-类-动态获取】      | bar =func1 | MyClass::{$bar}();        | ', MyClass::{$bar}() , PHP_EOL;
echo '【静态方法-对象-动态获取】    | bar =func1 | (new MyClass)::{$bar}();  | ', (new MyClass)::{$bar}() , PHP_EOL;

$bar = 'func2';
echo '【成员方法-对象-动态获取】    | bar =func2 | (new MyClass)->{$bar}();  | ', (new MyClass)->{$bar}() , PHP_EOL;

// 别忘记枚举值
enum Status: string {
    case S_OK = 'ok';
}

$bar = 'S_OK';
echo '【枚举值-枚举-动态获取】    | bar =S_OK | Status::{$bar}->value;  | ', Status::{$bar}->value , PHP_EOL;

0x4. Deep Clone readonly properties (只读属性深拷贝) ☔☔☔

用法

readonly class People
{
    public function __construct(
        public string $name,
        public string $age,
        public DateTime $createdAt,
    ) {}


    public function __clone()
    {
        $this->createdAt = new DateTime(); 
    }
}

$tacks = new People(
    name: 'Tacks',
    age: 18,
    createdAt: new DateTime(),
);

var_dump($tacks);

$nextMe = clone $tacks;

// php8.3 之前 Fatal error: Uncaught Error: Cannot modify readonly property People::$createdAt
// php8.3 可用 在克隆过程中可以重新初始化只读属性
var_dump($nextMe);

补充:匿名只读类也在 php8.3支持了

// php8.3 之前 Parse error: syntax error, unexpected token "readonly"
// php8.3 可用
$other = new readonly class {
    public function __construct(
        public string $foo = 'bar',
    ) {}
};
var_dump($other->foo);

0x5. Marking overriden methods with new attribute (标记重写方法 #[\Override]) ⭐⭐⭐⭐

过去&现在

  • before

使用 #[Override] 注解标记方法,表示您知道该方法正在重写父方法,它唯一要做的就是表现出意图,防止父类如果无意间被修改了方法名,将会导致子类的不可预见性错误。

  • now

但是现在,php8.3 有了 #[Override] 注解,让 PHP 知道了子类的这个方法是覆盖父类的,如果没有则会抛出错误,当然这也更加友好,因为如果你的 IDE 如果支持,或者有 静态分析器可以提前检查对应的错误。

Fatal error: MyClass::foo() has #[\Override] attribute, but no matching parent method exists

使用

abstract class Father
{
    public function foo(): int
    {
        return 1;
    }
}

final class MyClass extends Father
{
    #[Override]
    public function foo(): int
    {
        return 2; // The overridden method
    }
} 

0x6. New Randomizer::getFloat() and Randomizer::nextFloat() methods (Randomizer类新增两个浮点数方法) 💥💥💥💥💥

由于浮点数的精度有限和隐式舍入,生成位于特定区间内的无偏浮点数并非易事,并且常用的用户态解决方案可能会生成有偏差的结果或超出请求范围的数字。

所以说官方出手了,还是比自己封装的要高效好用,之后随机浮点数可以来参考这个了。

# 获取随机浮点数,在 min,max ,第三个参数则是决定是否包含边界
public Random\Randomizer::getFloat(float $min, float $max, Random\IntervalBoundary $boundary = Random\IntervalBoundary::ClosedOpen): float

# 获取 (0,1) 之间的浮点数 
public Random\Randomizer::nextFloat(): float

使用

//  新增 getFloat() nextFloat()

$randomizer = new \Random\Randomizer();

// getFloat($min, $max) 返回之间的浮点数
// Closed表示包含该值,表示Open排除该值
$temperature = $randomizer->getFloat(
    -89.2,
    56.7,
    \Random\IntervalBoundary::ClosedClosed,
);
var_dump($temperature);

// nextFloat() 它将为您提供 0 到 1 之间的随机浮点数,其中 1 被排除
var_dump($randomizer->nextFloat());

$chanceForTrue = 0.8;
$myBoolean = $randomizer->nextFloat() < $chanceForTrue;
var_dump($myBoolean);

0x7. Command line linter supports multiple files (支持 -l 检查多个文件) ⚡

过去&现在

说实话,用 php 也挺久的了,竟然没发现,或者说没用过 -l 选项。要不是这次看了 php8.3 的新功能首页 ,把这个功能明显的列出来,我还真不知道,哈哈哈哈哈。

  • before

php8.3 之前, php -l test.php 只支持单个文件,及时你写多个文件,也是以第一个文件为准

  • now

php8.3 时, php -l test1.php test2.php 支持多个文件了,会按照顺序依次检查

使用

$ php -l test1.php test2.php

Parse error: syntax error, unexpected end of file in test1.php on line 21
Errors parsing test1.php

Parse error: syntax error, unexpected token "{", expecting identifier in test2.php on line 5
Errors parsing test2.php

0x8. Negative indices in arrays (数组中的负数索引) ⛔

过去&现在

  • before

php8.3 之前, 第一个索引是负数,第二个默认还是 0 开始

  • now

php8.3 时,第一个索引是负数,第二个默认则是加一之后的索引,而不一定是 0

使用

$array = [];

$array[-9] = 'a';
$array[]   = 'b';

var_export($array);

/*

===> php8.3 之前
array (
  -9 => 'a',
  0 => 'b',
)


===> php8.3 时
array (
  -9 => 'a',
  -8 => 'b',
)
 */

0x9. New posix’s function (posix扩展相关函数的新增) 💧💧

0x10. Other

更多更新细节,可以看

堆栈溢出检测

PHP8.3 添加了两个新的 ini 指令

  • zend.max_allowed_stack_size
    • 0 意味着 PHP 将自动确定一个值
    • -1 表明没有限制或特定的字节数
    • 现有 fiber.stack_size指令用作允许的最大堆栈大小
  • zend.reserved_stack_size 用于确定“缓冲区”, 以便 PHP 仍然能够抛出错误而不是实际耗尽内存
    • 这里的值应该是多个字节,但 PHP 将为您确定一个合理的默认值,无需填写

好处: Error 当使用超过 和 zend.max_allowed_stack_size 之间的差异时,接近溢出调用堆栈的程序现在可能会抛出 zend.reserved_stack_size。此功能的好处是堆栈溢出引起的分段错误将不再导致段错误,从而使调试变得更加容易。

新 mb_str_pad() 功能

在 PHP 中,各种字符串函数有两种变体:一种用于字节字符串,另一种用于多字节字符串。然而,多字节字符串函数中值得注意的一个缺陷 mbstringstr_pad()

str_pad()函数缺乏多字节字符支持,因此在使用使用多字节编码(如 UTF-8)的语言时会出现问题。该 RFC 建议在 PHP 中添加这样一个函数,我们将其称为 mb_str_pad()

非RFC提交

并非 PHP 中的每个更改都会通过 RFC 流程。事实上,大多数更改包括维护和错误修复,并且不需要 RFC。所有这些更改都列在升级文档中。

  • 使用FFI时,返回类型为 void 现在返回 null 而不是 C 函数 FFI\CData:void
  • posix_getrlimit() 现在采用可选$res参数来允许获取单个资源限制
  • gc_status() 有四个新字段 running、protected、full 、buffer_size
  • class_alias()现在支持创建内部类的别名
  • array_pad() 现在仅受数组可以拥有的最大元素数的限制。之前,一次最多只能添加 1048576 个元素
  • 在 posix 系统上执行 proc_get_status() 多次将始终返回正确的值
  • opcache.consistency_checksini 指令已删除
  • 改进 array_sum()array_product()
php
本作品采用《CC 协议》,转载必须注明作者和本文链接
明天我们吃什么 悲哀藏在现实中 Tacks
本帖由系统于 3个月前 自动加精
讨论数量: 6

无关痛痒的更新

5个月前 评论
Tacks (楼主) 4个月前

还有一条,在Windows上,最低支持版本是 Windows 8 或 Windows Server 2012。

www.php.net/manual/en/migration83....

Minimum supported Windows version has been bumped to Windows 8 or Windows Server 2012.

3个月前 评论
Tacks (楼主) 3个月前
梦想星辰大海

介绍的非常详细,感谢!

3个月前 评论
Tacks (楼主) 3个月前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!