Aini99
@Aini99

PHP 8 预计将于 2020 年 12 月发布,它将为我们带来很多强大的功能和出色的语言改进。许多 RFC 已经获得批准和实施,因此现在该是我们深入研究一些最令人兴奋的新增内容的原因了,它们应该使 PHP 更快,更可靠。

由于 PHP 8 仍在开发中,因此我们可以在最终版本之前看到一些变化。我们将跟踪这些更改并定期更新此帖子,因此请确保你不会错过任何有关 PHP 8 的信息,并不时检查该帖子。那么,我们期望 PHP 8 有哪些功能和改进?PHP 8(该语言的下一个主要版本)最大的特点是什么?

PHP JIT(及时编译器)

PHP 8 附带的最受赞誉的功能是即时(JIT)编译器。JIT 的全部意义是什么?RFC 提案将 JIT 描述如下:

“PHP JIT 被实现为 OPcache 的几乎独立的部分。它可以在 PHP 编译时和运行时启用 / 禁用。启用后,PHP 文件的本机代码将存储在 OPcache 共享内存的其他区域中,并且 op_array→opcodes []。handler 将指针指向 JIT 版本代码的入口点。”

那么,我们如何实现 JIT?JIT 与 OPcache 有何区别?为了更好地理解什么是 JIT for PHP,让我们快速看一下 PHP 如何从源代码执行到最终结果。PHP 执行是一个 4 个阶段的过程:

  1. Lexing / 令牌化:首先,解释器读取 PHP 代码并构建一组令牌。
  2. 解析:解释器检查脚本是否与语法规则匹配,并使用标记来构建抽象语法树(AST),该树是源代码结构的分层表示。
  3. 编译:解释器遍历树并将 AST 节点转换为低级 Zend 操作码,这些操作码是确定 Zend VM 执行的指令类型的数字标识符。
  4. 解释:操作码将在 Zend VM 上解释并运行。

下图显示了基本 PHP 执行过程的直观表示。

基本的PHP执行过程

那么,OPcache 如何使 PHP 更快?JIT 执行过程中有哪些变化?

OPcache 扩展

PHP 是一种解释型语言。这意味着,当运行 PHP 脚本时,解释器将在每次请求时一遍又一遍地解析,编译和执行代码。这可能会浪费 CPU 资源和其他时间。

这是 OPcache 扩展程序发挥作用的地方:

“OPcache 通过将预编译的脚本字节码存储在共享内存中来提高 PHP 性能,从而消除了 PHP 在每个请求上加载和解析脚本的需要。”

启用 OPcache 后,PHP 解释程序仅在脚本首次运行时才经过上述 4 个阶段。由于 PHP 字节码存储在共享内存中,因此它们可以立即作为低级中间表示形式使用,并且可以立即在 Zend VM 上执行。

启用OPcache的PHP执行过程

从 PHP 5.5 开始,Zend OPcache 扩展默认情况下可用,你可以通过简单地从服务器上的脚本调用 phpinfo() 或签出 php.ini 文件(请参阅 OPcache 配置设置)来检查是否正确配置了它。

预装

OPcache 最近通过预加载的实现进行了改进,该预加载是 PHP 7.4 中新增的 OPcache 新功能。预加载提供了一种 “在运行任何应用程序代码之前” 将一组指定的脚本存储到 OPcache 内存中的方法,但是对于典型的基于 Web 的应用程序而言,它不会带来明显的性能提升。

JIT — 及时编译器

即使操作码采用低级中间表示形式,也仍然必须将其编译为机器代码。JIT“不引入任何其他 IR(中间表示)形式”,而是使用 DynASM(用于代码生成引擎的动态汇编器)直接从 PHP 字节码生成本机代码。

简而言之,JIT 将中间代码的热门部分转换为机器代码。绕过编译,它将能够显着提高性能和内存使用率。

实时 Web 应用的 JIT

根据 JIT RFC,即时编译器实现应提高 PHP 性能。但是,我们真的会在 WordPress 等现实应用中体验到这种改进吗?

早期测试表明,JIT 可以使 CPU 密集型工作负载的运行速度大大提高,但是 RFC 警告:

“…… 像以前的尝试一样,它目前似乎并不能显着改善 WordPress 等现实应用程序(opcache.jit = 1235 326 req /sec 与 315 req /sec)。 计划通过使用性能分析和推测性优化来付出更多的努力,以改善实际应用的 JIT。”

启用 JIT 后,代码将不会由 Zend VM 运行,而是由 CPU 本身运行,这将提高计算速度。诸如 WordPress 之类的 Web 应用程序还依赖于其他因素,例如 TTFB,数据库优化,HTTP 请求等。

因此,当涉及到 WordPress 和类似应用程序时,我们不应该期望 PHP 的执行速度会大大提高。但是,JIT 可以为开发人员带来一些好处。

根据尼基塔・波波夫(Nikita Popov)的说法:

“ JIT 编译器的好处大致(如 RFC 中所述):

  • 数字代码的性能明显更好。
  • “典型” PHP Web 应用程序代码的性能略好。
  • 将更多代码从 C 转移到 PHP 的潜力,因为 PHP 现在已经足够快了。”

因此,尽管 JIT 几乎不会给 WordPress 性能带来巨大的改善,但它将把 PHP 升级到一个新的水平,从而使它现在可以直接用多种功能编写。不过,不利的一面是更大的复杂性,它可能导致维护,稳定性和调试成本增加。根据 Dmitry Stogov 的说法:

“JIT 非常简单,但无论如何,它会增加整个 PHP 复杂性的水平,增加新错误的风险以及开发和维护成本。”

PHP 8 改进和新功能

除了 JIT 之外,我们还可以期望 PHP 8 带来许多功能和改进。以下是我们精选的即将到来的新增内容和更改,这些内容应使 PHP 更加可靠和高效。

  1. 验证抽象特征方法
  2. 不兼容的方法签名
  3. 以负索引开头的数组
  4. 联合类型 2.0
  5. 内部函数的一致类型错误
  6. 抛出表情
  7. 弱地图
  8. 参数列表中的尾部逗号
  9. 在对象上允许:: class 语法
  10. 属性 v2

验证抽象特征方法

特性被定义为 “一种在单一继承语言(例如 PHP)中代码重用的机制”。通常,它们用于声明可在多个类中使用的方法。特征也可以包含抽象方法。这些方法只是声明方法的签名,但是方法的实现必须使用 trait 在类中完成。

这也意味着方法的签名必须匹配。换句话说,所需参数的类型和数量必须相同。无论如何,根据 RFC 的作者 Nikita Popov 的说法,签名验证当前仅是强制实施的:

Nikita 的以下示例涉及第一种情况(非强制签名):

trait T {
    abstract public function test(int $x);
}

class C {
    use T;

    // Allowed, but shouldn't be due to invalid type.
    public function test(string $x) {}
}

话虽如此,如果实现方法与抽象特征方法不兼容,则无论其来源如何,该 RFC 都建议始终引发致命错误:

Fatal error: Declaration of C::test(string $x) must be compatible with T::test(int $x) in /path/to/your/test.php on line 10

不兼容的方法签名

在 PHP 中,由于方法签名不兼容而导致的继承错误会引发致命错误或警告,具体取决于导致错误的原因。如果类正在实现接口,则不兼容的方法签名将引发致命错误。根据对象接口文档:

“实现接口的类必须使用与 LSP(Liskov 替换原理)兼容的方法签名。不这样做将导致致命错误。”

这是一个带有接口的继承错误的示例:

interface I {
    public function method(array $a);
}
class C implements I {
    public function method(int $a) {}
}

在 PHP 7.4 中,上面的代码将引发以下错误:

Fatal error: Declaration of C::method(int $a) must be compatible with I::method(array $a) in /path/to/your/test.php on line 7

子类中具有不兼容签名的函数将引发警告。请参阅 RFC 中的以下代码:

class C1 {
    public function method(array $a) {}
}
class C2 extends C1 {
    public function method(int $a) {}
}

在 PHP 7.4 中,以上代码将简单地发出警告:

Warning: Declaration of C2::method(int $a) should be compatible with C1::method(array $a) in /path/to/your/test.php on line 7

现在,该 RFC 建议始终为不兼容的方法签名引发致命错误。使用 PHP 8,我们在上文中看到的代码将提示以下内容:

Fatal error: Declaration of C2::method(int $a) must be compatible with C1::method(array $a) in /path/to/your/test.php on line 7

以负索引开头的数组

在 PHP 中,如果数组以负索引(start_index < 0)开头,则以下索引将从 0 开始(有关更多信息,请参见 array_fill 文档)。看下面的例子:

$a = array_fill(-5, 4, true);
var_dump($a);

在 PHP 7.4 中,结果如下:

array(4) {
    [-5]=>
    bool(true)
    [0]=>
    bool(true)
    [1]=>
    bool(true)
    [2]=>
    bool(true)
}

现在,该 RFC 建议更改内容,以使第二个索引为 start_index + 1,无论的值如何都为 start_index

在 PHP 8 中,上面的代码将导致以下数组:

array(4) {
    [-5]=>
    bool(true)
    [-4]=>
    bool(true)
    [-3]=>
    bool(true)
    [-2]=>
    bool(true)
}

使用 PHP 8,以负索引开头的数组会更改其行为。在 RFC 中阅读有关向后不兼容的更多信息。

联合类型 2.0

联合类型接受可以是不同类型的值。目前,除了?Type 语法和特殊 iterable 类型外,PHP 不支持联合类型。在 PHP 8 之前,联合类型只能在 phpdoc 批注中指定,如 RFC 中的以下示例所示:

class Number {
    /**
     * @var int|float $number
     */
    private $number;

    /**
     * @param int|float $number
     */
    public function setNumber($number) {
        $this-&gt;number = $number;
    }

    /**
     * @return int|float
     */
    public function getNumber() {
        return $this->number;
    }
}

现在,联合类型 2.0 RFC 提议在函数签名中添加对联合类型的支持,这样我们就不再依赖内联文档,而是使用 T1|T2|... 语法来定义联合类型:

class Number {
    private int|float $number;

    public function setNumber(int|float $number): void {
        $this-&gt;number = $number;
    }

    public function getNumber(): int|float {
        return $this->number;
    }
}

正如 Nikita Popov 在 RFC 中所述:

“通过该语言支持联合类型,我们可以将更多类型信息从 phpdoc 移到函数签名中,这具有通常的优点:

  1. 类型实际上是强制执行的,因此可以及早发现错误。
  2. 因为它们是强制性的,所以类型信息不太可能变得过时或遗漏边缘情况。
  3. 在继承过程中检查类型,以执行 Liskov 替换原则。
  4. 可通过反射获得类型。
  5. 语法比 phpdoc 少了很多。”

联合类型支持所有可用类型,但有一些限制:

  1. void 类型不能是并集的一部分,因为这 void 意味着函数不返回任何值。
  2. null 类型仅支持工会的类型,但它的使用作为一个独立的类型是不允许的。
  3. ?T 也可以使用可为 null 的类型表示法 (),即 T|null,但不允许?T 在联合类型中包含该表示法 (?T1|T2 不允许,我们应改用 T1|T2|null)。
  4. 尽可能多的功能(即 strpos()strstr()substr() 等等)包括 false 可能的返回类型中,所述 false 伪型也支持。

内部函数的一致类型错误

传递非法类型的参数时,内部函数和用户定义函数的行为会有所不同。用户定义的函数会引发 TypeError,但是内部函数会根据多种条件以多种方式运行。无论如何,典型的行为是发出警告并返回 null。请参见 PHP 7.4 中的以下示例:

var_dump(strlen(new stdClass));

这将导致以下警告:

Warning: strlen() expects parameter 1 to be string, object given in /path/to/your/test.php on line 4
NULL

如果 strict_types 启用,或参数信息指定类型,则行为将有所不同。在这种情况下,将检测到类型错误并导致 TypeError。

为了消除这些不一致,该 RFC 建议使内部参数解析 API 始终 ThrowError 在参数类型不匹配的情况下生成。在 PHP 8 中,上面的代码引发以下错误:

Fatal error: Uncaught TypeError: strlen(): Argument #1 ($str) must be of type string, object given in /path/to/your/test.php:4
Stack trace:
#0 {main}
thrown in /path/to/your/test.php on line 4

抛出表达式

在 PHP 中,throw 是一个 statement,因此不可能在只允许使用表达式的地方使用它。

该 RFC 建议将 throw 语句转换为表达式,以便可以在允许表达式的任何上下文中使用。例如,箭头函数,空合并运算符,三元运算符等。

请参阅 RFC 中的以下示例:

$callable = fn() => throw new Exception();

// $value is non-nullable.
$value = $nullableValue ?? throw new InvalidArgumentException();

// $value is truthy.
$value = $falsableValue ?: throw new InvalidArgumentException();

弱映射

弱映射是弱引用键的数据(对象)的集合,这意味着不会阻止对它们的垃圾回收。PHP 7.4 增加了对弱引用的支持,以此作为保留对对象的引用的一种方式,这种引用不会阻止对象本身被销毁。正如 Nikita Popov 所指出的,

“原始的弱引用本身仅具有有限的用途,而弱映射在实践中更为常用。由于未提供注册销毁回调的功能,因此不可能在 PHP 弱引用之上实现有效的弱映射。”

这就是为什么该 RFC 引入了一个 WeakMap 类来创建用作弱映射键的对象,如果没有对该键对象的进一步引用,则可以将该对象破坏并从弱映射中删除。在长时间运行的进程中,这将防止内存泄漏并提高性能。请参阅 RFC 中的以下示例:

$map = new WeakMap;
$obj = new stdClass;
$map[$obj] = 42;
var_dump($map);

使用 PHP 8,上面的代码将产生以下结果:

object(WeakMap)#1 (1) {
    [0]=>
    array(2) {
        ["key"]=>
        object(stdClass)#2 (0) {
        }
        ["value"]=>
        int(42)
    }
}

如果你取消设置对象,则键会自动从弱映射中删除:

unset($obj);
var_dump($map);

现在的结果如下:

object(WeakMap)#1 (0) {
}

参数列表中的尾部逗号

尾随逗号是附加到不同上下文中项目列表的逗号。PHP 7.2 在列表语法中引入了结尾逗号,PHP 7.3 在函数调用中引入了结尾逗号。

PHP 8 现在在参数列表中以函数,方法和闭包形式引入尾随逗号,如以下示例所示:

class Foo {
    public function __construct(
        string $x,
        int $y,
        float $z, // trailing comma
    ) {
        // do something
    }
}

该提案以 58 票对 1 票获得通过。

在对象上允许:: class 语法

为了获取类的名称,我们可以使用 Foo\Bar::class 语法。该 RFC 建议将相同的语法扩展到对象,以便现在可以获取给定对象的类的名称,如下例所示:

$object = new stdClass;
var_dump($object::class); // "stdClass"

$object = null;
var_dump($object::class); // TypeError

使用 PHP 8,$object::class 提供与相同的结果 get_class($object)。如果 $object 不是对象,则抛出 TypeError 异常。

属性 v2(Attributes v2)

属性,也称为注释,是结构化元数据的一种形式,可用于指定对象,元素或文件的属性。

在 PHP 7.4 之前,文档注释是将元数据添加到类,函数等的声明中的唯一方法。现在,Attributes v2 RFC 引入了 PHP 的属性,将它们定义为结构化的语法元数据的形式,可以将其添加到类,属性,函数,方法,参数和常量。

将属性添加到它们所引用的声明之前。请参阅 RFC 中的以下示例:

<<ExampleAttribute>>
class Foo
{
    <<ExampleAttribute>>
    public const FOO = 'foo';

    <<ExampleAttribute>>
    public $x;

    <<ExampleAttribute>>
    public function foo(<<ExampleAttribute>> $bar) { }
}

$object = new <<ExampleAttribute>> class () { };

<<ExampleAttribute>>
function f1() { }

$f2 = <<ExampleAttribute>> function () { };

$f3 = <<ExampleAttribute>> fn () => 1;

可以在文档块注释之前或之后添加属性:

<<ExampleAttribute>>
/* docblock /
<<AnotherExampleAttribute>>
function foo() {}

每个声明可以具有一个或多个属性,并且每个属性可以具有一个或多个关联值:

<<WithoutArgument>>
<<SingleArgument(0)>>
<<FewArguments('Hello', 'World')>>
function foo() {}

新的 PHP 函数

PHP 8 为该语言带来了几个新功能:

  1. str_contains
  2. str_starts_with()和 str_ends_with()
  3. get_debug_type

str_contains

在 PHP 8 之前,strstr 和 strpos 是开发人员在给定字符串中搜索针的典型选择。问题是,这两个函数并不是很直观,它们的用法对于新的 PHP 开发人员可能会造成混淆。请参见以下示例:

$mystring = 'Managed WordPress Hosting';
$findme = 'WordPress';
$pos = strpos($mystring, $findme);

if ($pos !== false) {
    echo "The string has been found";
} else {
    echo "String not found";
}

在上面的示例中,我们使用了!== 比较运算符,该运算符还检查两个值是否属于同一类型。如果针的位置为 0,这可以防止我们出错:

“此函数可以返回布尔 FALSE,但也可以返回非布尔值,其值为 FALSE。[…] 使用 === 运算符测试此函数的返回值。”

此外,一些框架提供了帮助程序功能来搜索给定字符串内的值(请参阅 Laravel 帮助程序文档作为示例)。现在,该 RFC 建议引入一个新功能,该功能允许在字符串内部进行搜索:

str_contains ( string $haystack , string $needle ) : bool

它的用法非常简单。str_contains 检查是否 $needle 在中找到 $haystack 并返回 true 或相应地返回 false

因此,使用 str_contains,我们可以编写以下代码:

$mystring = 'Managed WordPress Hosting';
$findme   = 'WordPress';

if (str_contains($mystring, $findme)) {
    echo "The string has been found";
} else {
    echo "String not found";
}

在撰写本文时,str_contains 它区分大小写,但是将来可能会改变。

str_starts_with () 和 str_ends_with ()

除此 str_contains 功能外,还有两个新功能允许在给定的字符中搜索:str_starts_withstr_ends_with

这些新函数检查给定字符串是否以另一个字符串开头或结尾:

str_starts_with (string $haystack , string $needle) : bool
str_ends_with (string $haystack , string $needle) : bool

根据该 RFC 的作者 Will Hudgins 所说:

str_starts_withstr_ends_with 功能是如此普遍,以至于许多主要的 PHP 框架都支持它,包括 Symfony,Laravel,Yii,FuelPHP 和 Phalcon。”

我们现在可能避免使用次优和喜欢不太直观的功能 substrstrpos。这两个函数都区分大小写:

$str = "WordPress";
if (str_starts_with($str, "Word")) echo "Found!";

if (str_starts_with($str, "word")) echo "Not found!";

get_debug_type

get_debug_type 是一个新的 PHP 函数,它返回变量的类型。新函数的工作方式与该 gettype 函数非常相似,但是 get_debug_type 返回本机类型名称并解析类名称。

对于语言而言,这是一个很好的改进,因为 gettype() 对于类型检查而言,它没有用。RFC 提供了两个有用的示例,可以更好地理解新 get_debug_type() 功能和的区别 gettype()。第一个示例显示 gettype 了工作方式:

$bar = [1,2,3];

if (!($bar instanceof Foo)) { 
    throw new TypeError('Expected ' . Foo::class . ', got ' . (is_object($bar) ? get_class($bar) : gettype($bar)));
}

在 PHP 8 中,我们可以使用 get_debug_type

if (!($bar instanceof Foo)) { 
    throw new TypeError('Expected ' . Foo::class . ' got ' . get_debug_type($bar));
}

下表显示了 get_debug_type 和的返回值 gettype

gettype() get_debug_type()
1 个 整数 整型
0.1 浮动
真正 布尔值 布尔
布尔值 布尔
空值 空值 空值
“ WordPress”
[1,2,3] 数组 数组
名称为 “Foo Bar” 的类 宾语 Foo Bar
匿名 Class 宾语 class @ anonymous

其他 RFC

在撰写本文时,一些针对 PHP 8 的 RFC 仍在起草和 / 或实施中。一旦其状态更改为 “已实施”,我们将立即添加它们。

这是 PHP 8 包含的其他已批准改进的快速列表:

  1. Stringable 接口:此 RFC 引入了 Stringable 接口,该接口会自动添加到实现该__to String () 方法的类中。这里的主要目标是使用 string|Stringable 联合类型。
  2. ext /dom 中的新 DOM Living Standard API:此 RFC 建议通过引入新的接口和公共属性,将当前的 DOM Living Standard 实施为 PHP DOM 扩展。
  3. 静态返回类型:PHP 8 在 selfparent 类型旁边引入了 static 作为返回类型的用法。
  4. 变量语法调整:此 RFC 解决了 PHP 变量语法中的一些残留不一致之处。
2020 年 06 月 26 日 · 07:57
445
0
1
发表留言

PHP 8 的新增功能(JIT 编译器、新功能、变动 )
扫描右侧二维码继续阅读
June 26, 2020
Aini99
by yoniu.

Aini99