PHP 8.2已经发布。与8.0和8.1相比,这是一个次要版本。这可以部分归因于Nikita Popov作为PHP语言最重要的贡献者之一的离开。

PHP语言有很多维护者,并不是一个人创建的,但Nikita是其中最活跃的人之一。Nikita添加了联合类型、类型化属性、命名参数和箭头函数等功能。

Nikita离开的消息传遍了PHP社区,不得不承认一个重大问题。JavaScript/TypeScript、C# 和Java等其他语言得到了拥有许多资产的大公司的支持。

与PHP核心团队形成鲜明对比的是,PHP核心团队主要依靠志愿者和一些小公司支付一些贡献者。

由于最近的新闻和缺乏付费核心开发人员,PHP语言的未来不明朗,是2021年11月成立PHP 基金会的主要动机。

他们的使命宣言:“PHP 基金会将是一个非盈利组织,其使命是确保 PHP 语言的长寿和繁荣。”。

PHP基金会得到PHP社区中的公司和个人的支持,并将资助从事PHP核心工作的开发人员,以保持该语言像过去几年一样向前发展。

PHP 8.2发布恰逢PHP基金会成立的第一年,必须建立一些组织结构。在此期间,第一批开发人员被选中并最终受聘为基金会工作。

让我们来看看 PHP 8.2 中包含的一些改进。

移除动态属性

PHP一直具有动态特性。社区认为,在过去的几年里,该语言最早的一些特性过于动态。

动态属性提供了看不见的灵活性,但也为意想不到的行为打开了大门,从而导致难以修复的错误。

这种动态特性的一个很好的例子是动态属性。允许在任何对象上设置类中不存在的属性。

class User{}

$user = new User();
$user->email = 'info@spatie.be';

假设您在某处重命名此属性。然后你要找到所有使用这个属性的地方,因为它需要重命名。

静态分析和IDE在这里可以为您提供一些帮助。但是在你的类中定义属性,总比动态设置更好掌握之中。而且您可能也会写出更少的错误。

此外,读取未定义在类的属性会给你反馈,因为你会收到警告。而添加未定义的属性到类可以在没有任何警告的情况下完成。

PHP需要一个稳定的类型系统。动态属性违反了这条规则,这就是PHP不使用它们的原因。

从PHP 8.2开始,动态属性被弃用。上面的示例现在将抛出弃用警告。当您实现魔法__get()__set()方法时,获取和设置对象的动态属性仍然有效。

但您仍然可以通过将AllowDynamicProperties属性添加到类来启用动态属性的功能。

#[AllowDynamicProperties]
class User{}

$user = new User();
$user->email = 'web@myfreax.com';

最后,只读类永远不能有动态属性。将AllowDynamicProperties属性添加到只读类将导致错误。

字符串插值弃用

字符串插值是一个很棒的功能,它允许您像这样"Hello there, {$name}"在字符串中使用变量。

你也可以这样做"Hello there, ${name}"。尽管语法看起来几乎相同,但其行为却大不相同。

由于这些语义差异和使用字符串插值缺陷,该功能在PHP 8.2发布时被弃用。

"Hello there, {$name}"
"Hello there, ${name}"

敏感参数

PHP允许您在出现问题时查看堆栈跟踪以及与每个堆栈帧关联的所有参数。这对调试非常有帮助。

但对敏感数据来说可能是灾难性的。假设您的登录函数中存在密码。你密码现在包含在由异常创建的堆栈跟踪中。

这意味着任何人都可以在您错误配置应用程序时看到它。当您配置外部错误跟踪服务时,密码将与一堆调试数据一起发送到外部服务。

function login(
    string $name,
    string $password
) {
    throw new Exception('Whoops!');
}

您可能希望避免这种情况,您可以捕获错误并从第一个堆栈帧中转储参数来轻松检查。

try {
    login('Freek', 'secret');
} catch (Exception $exception) {
    var_dump($exception->getTrace()[0]['args']);
}
array(2) {
  [0]=> string(5) "myfreax"
  [1]=> string(6) "secret"
}

在PHP 8.2,添加了一个属性SensitiveParameter,用于替换堆栈跟踪中的参数。你可以这样使用它。

function login(
    string $name,
    #[SensitiveParameter]
    string $password
) {
    throw new Exception('Whoops!');
}
array(2) {
  [0]=> string(5) "Freek"
  [1]=> object(SensitiveParameterValue)#2 (0) {}
}

trait 常量

在PHP 8.2之前,不允许将常量添加到trait中。这是一种语言差异,因为trait可以访问使用它们的类的常量。

但是你不能严格地定义一个常量存在于一个类中,次问题已经在PHP 8.2修复。

trait WithSpatieApi {
    protected const SPATIE_API_VERSION = 1;
}

PCRE 非捕获改性剂

PHP 8.2正则表达式扩展的一个小补充是no-capture修饰符,它只会捕获命名的捕获组。

您可以通过附加n到正则表达式的末尾来添加此修饰符。假设您要捕获这些书面数字缩写1st2nd3rd等。

你可以像这样写一个正则表达式preg_match('/([0-9]+)(st|nd|rd|th)/', '10th', $matches);

使用命名的捕获组,您可以轻松命名所需的组preg_match('/([0-9]+)(?P:st|nd|rd|th)/', '5th', $matches);

使用 no-capture 修饰符,PHP将只包含命名组preg_match('/([0-9]+)(?P:st|nd|rd|th)/n', '5th', $matches);

// [0 => '5th',1 => '5', 2 => 'th']
preg_match('/([0-9]+)(st|nd|rd|th)/', '10th', $matches); 
// [0 => '5th', 1 => 5, 'abbreviation' => 'th', 2 => 'th']
preg_match('/([0-9]+)(?P<abbreviation>:st|nd|rd|th)/', '5th', $matches);
// [0 => '5th', 'abbreviation' => 'th', 1 => 'th']
preg_match('/([0-9]+)(?P<abbreviation>:st|nd|rd|th)/n', '5th', $matches);

随机扩展

PHP 8.2内置一个新的扩展,它增加了一种更适合OOP的方式来处理随机数生成和其他随机化操作。

添加一个类Randomizer启用此特性。您可以使用此特性来打乱字符串或者打乱一个数组,在指定的数值范围获取随机数。

use Random\Randomizer;

$randomizer = new Randomizer();

$randomizer->shuffleBytes('Hello World'); // example output: "eWrdllHoo l"

$randomizer->shuffleBytes(['a', 'b', 'c', 'd']); // example output: ['b', 'd', 'a', 'c']


$randomizer->getInt(0, 100); // example output: 42

另一个额外的好处是您可以为随机发生器提供一个伪随机数生成器PRNG。PHP有几个内置的PRNG引擎。

分别是Mt19937PcgOneseq128XslRr64Xoshiro256StarStarSecure。请注意,只有最后一个引擎适用于加密随机数生成。

这种引擎模型允许在未来添加更多安全/快速的引擎。您甚至可以创建与随机发生器一起使用的引擎。

use Random\Engine\Mt19937;
use Random\Randomizer;

$randomizer = new Randomizer(new Mt19937());
设置随机发生器的引擎

最后,您现在还可以设置用于随机发生器的引擎的种子。种子主要是一个随机值,从中导出其他随机值。

这种行为在测试或本地环境中很有用,如果在调试期间重新启动一段代码每次都会提供相同的结果,这将很有帮助。即使正在使用随机化操作。

当你运行这个例子两次时,每次生成的数字都会不同。当您使用Mt19937引擎为种子设置值时,随机生成的值每次都是相同的。

$randomizer = new Randomizer();

$randomizer->getInt(0, 100);
$randomizer = new Randomizer(new Mt19937(42));

$randomizer->getInt(0, 100);

只读类

有时你想创建一个不可变DTO,它只包含只读属性。在这种情况下,您可以创建一个类并将所有属性定义为只读。

这样做可能会非常耗时,每次添加新属性时,都应该记住将其设置为只读。PHP 8.2为这个问题引入了一个更好的解决方案。

那就是只读类。使用只读类,您将类声明一次为只读,此刻起所有属性都将是只读的。

你不能创建扩展非只读类从只读类,也不可能给只读类添加静态属性,这将会导致致命错误

class CustomerDTO
{
    public function __construct(
        public readonly string $name, 
        public readonly string $email, 
        public readonly DateTimeImmutable $birth_date,
    ) {}
}
public readonly class CustomerDTO
{
    public function __construct(
        string $name, 
        string $email, 
        DateTimeImmutable $birth_date,
    ) {}
}

弃用语法

PHP 8.2将弃用一些可调用的语法,因为它们不一致。这些语法可以与callable类型、函数is_callable()call_user_func()一起使用。

奇怪的是,这些语法在某些情况下可以使用,但不能与这样的$callable()语法一起使用。

此外,这些语法是上下文相关的。self和后面的类型static可能会根据调用它们的位置而改变,这可能会在调用私有方法时导致意外行为。

这就是为什么不推荐将它们与callable类型is_callable()call_user_func()函数一起使用的原因。

"self::method"
"parent::method"
"static::method"
["self", "method"]
["parent", "method"]
["static", "method"]
["Foo", "Bar::method"]
[new Foo, "Bar::method"]

以下语法仍然可以使用:

'function'
['SomeClass', 'someMethod']
'SomeClass:: someMethod']
[new SomeClass(), 'someMethod']
[$this, 'someMethod']
Closure::fromCallable()
function(...)

重置内存峰值函数

在PHP,可以测量峰值内存使用情况。以前不可能重置此峰值,因此不可能在第一个峰值之后测量第二个峰值。

在PHP 8.2添加memory_reset_peak_usage函数,允许您重置此内存峰值,以便您可以再次测量它。

例如,当您创建两个数组并想要测量第二个数组的内存使用峰值时,该值将等于创建第一个数组时的内存使用峰值。

range(0, 100_000);

memory_get_peak_usage(); // 2509760

range(0, 100);

memory_get_peak_usage(); // 2509760
range(0, 100_000);

memory_get_peak_usage(); // 2509760

memory_reset_peak_usage();

range(0, 100);

memory_get_peak_usage(); // 398792

mysqli::execute_query方法

PHP 8.2为mysqli扩展添加一个新方法,允许您在一个方法中执行准备、绑定参数和执行SQL语句。

过去,执行一个查询需要很多操作。这段代码现在可以重写为$mysqli->execute_query('SELECT * FROM posts WHERE id = ?', [$id])

$query = 'SELECT * FROM posts WHERE id = ?';
$statement = $connection->prepare($query);
$statement->bind_param('i', $id);
$statement->execute();
$result = $statement->get_result();
$result = $mysqli->execute_query('SELECT * FROM posts WHERE id = ?', [$id]);