PHP 8的新增功能(功能,改进和JIT编译器)

PHP 8预计将于2020年12月发布,它将为我们带来很多强大的功能和出色的语言改进。

#js-mykinsta-video {
背景图片:url(https://kinsta.com/wp-content/themes/kinsta/images/mykinsta-dashboard-v8@2x.jpg);
}

免费试用

尽管仍有一些提案正在开发中,但许多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→o​​pcodes[].handler保留指向JIT版本代码的入口点的指针。”

那么,我们如何实现JIT?JIT与OPcache有何区别?

为了更好地了解什么是JIT for PHP,让我们快速看一下PHP如何从源代码执行到最终结果。

PHP执行是一个4个阶段的过程:

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

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

基本的PHP执行过程

基本的PHP执行过程

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

OPcache扩展

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

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

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

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

启用OPcache的PHP执行过程

启用OPcache的PHP执行过程

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

phpinfo页面中的Zend OPcache部分

phpinfo页面中的Zend OPcache部分

预装

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

您可以在我们的PHP 7.4简介中阅读有关预加载的更多信息。

通过JIT,PHP向前迈了一步。

JIT —及时编译器

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

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

PHP JIT提案的合著者Zeev Surasky显示了使用JIT可以更快地进行多少计算:

但是,JIT是否可以有效地提高WordPress性能?

实时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复杂性的水平,增加新错误的风险以及开发和维护成本。”

将JIT纳入PHP 8的提案以50票对2票获得通过。

PHP 8改进和新功能

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

验证抽象特征方法

特性被定义为“一种在单一继承语言(例如PHP)中代码重用的机制”。通常,它们用于声明可在多个类中使用的方法。

特征也可以包含抽象方法。这些方法只是声明方法的签名,但是该方法的实现必须在使用trait的类中完成。

根据PHP手册,

“特质支持对抽象方法的使用,以便对展出的类强加要求。”

这也意味着方法的签名必须匹配。换句话说,所需参数的类型和数量必须相同。

无论如何,根据RFC的作者Nikita Popov的说法,签名验证目前仅是强制实施的:

  • 在最常见的情况下,如果使用using类提供方法实现,则不强制实施:https://3v4l.org/SeVK3
  • 如果实现来自父类,则将强制实施:https://3v4l.org/4VCIp
  • 如果实现来自子类,则将强制实施:https://3v4l.org/q7Bq2

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

性状T {
抽象公共功能测试(int $ x);
}
 
C级{
用T;

//允许,但不应由于类型无效而导致。
公共功能测试(字符串$ x){}
}

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

致命错误:第10行的/path/to/your/test.php中的C :: test(string $ x)声明必须与T :: test(int $ x)兼容

该RFC已获得一致批准。

使用WordPress,我们的流量增长了1,187%。
我们将向您展示如何。
加入20,000多个其他人,他们每周都会收到有关WordPress内部技巧的新闻!

          现在订阅
        
        
          
            
            成功!感谢您的订阅

您将在一周内收到下一期的Kinsta新闻通讯。

订阅Kinsta新闻通讯
        
  
    
      
    
      
        订阅
    
  
  
    

我同意条款和条件以及隐私政策

不兼容的方法签名

在PHP中,由于方法签名不兼容而导致的继承错误会引发致命错误或警告,具体取决于导致错误的原因。

如果类正在实现接口,则不兼容的方法签名将引发致命错误。根据对象接口文档:

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

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

接口我{
公共函数方法(数组$ a);
}
C类实现了我{
公共函数方法(int $ a){}
}

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

致命错误:C :: method(int $ a)的声明必须与第7行/path/to/your/test.php中的I :: method(array $ a)兼容

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

C1级{
公共函数方法(数组$ a){}
}
C2类扩展了C1 {
公共函数方法(int $ a){}
}

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

警告:C2 :: method(int $ a)的声明应与第7行/path/to/your/test.php中的C1 :: method(array $ a)兼容

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

致命错误:C2 :: method(int $ a)的声明必须与第7行/path/to/your/test.php中的C1 :: method(array $ a)兼容

以负索引开头的数组

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

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

在PHP 7.4中,结果如下:

数组(4){
[-5]=>
布尔值(true)
[0]=>
布尔值(true)
[1]=>
布尔值(true)
[2]=>
布尔值(true)
}

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

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

数组(4){
[-5]=>
布尔值(true)
[-4]=>
布尔值(true)
[-3]=>
布尔值(true)
[-2]=>
布尔值(true)
}

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

联合类型2.0

联合类型接受可以是不同类型的值。目前,除?Type语法和特殊的可迭代类型外,PHP不支持联合类型。

在PHP 8之前,联合类型只能在phpdoc批注中指定,如RFC中的以下示例所示:

班级编号{
/ **
* @var int | float $ number
* /
私人$ number;

/ **
* @param int | float $ number
* /
公共功能setNumber($ number){
$ this-> number = $ number;
}

/ **
* @return int | float
* /
公共函数getNumber(){
返回$ this-> number;
}
}

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

班级编号{
private int | float $ number;

公共函数setNumber(int | float $ number):void {
$ this-> number = $ number;
}

公共函数getNumber():int | float {
返回$ this-> number;
}
}

正如Nikita Popov在RFC中所述,

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

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

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

  • void类型不能成为并集的一部分,因为void意味着函数不返回任何值。
  • null类型仅在联合类型中受支持,但不允许将其用作独立类型。
  • 也可以使用可为空的类型表示法(?T),表示T | null,但不允许在联合类型中包含?T表示法(不允许使用?T1 | T2,而应使用T1 | T2 | null) 。
  • 由于许多函数(例如strpos(),strstr(),substr()等)在可能的返回类型中包括false,因此也支持false伪类型。

您可以在RFC中阅读有关联合类型V2的更多信息。

内部函数的一致类型错误

传递非法类型的参数时,内部函数和用户定义函数的行为会有所不同。

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

var_dump(strlen(new stdClass));

这将导致以下警告:

警告:strlen()期望参数1为字符串,第4行的/path/to/your/test.php中给出的对象
空值

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

这种情况将导致许多问题,这些问题在RFC的问题部分中得到了很好的解释。

为了消除这些不一致,此RFC建议使内部参数解析API在参数类型不匹配的情况下始终生成ThrowError。

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

致命错误:未被捕获的TypeError:strlen():参数#1($ str)必须为字符串类型,对象在/path/to/your/test.php:4中给出
堆栈跟踪:
#0 {main}
  在第4行的/path/to/your/test.php中抛出

抛出表情

在PHP中,throw是一条语句,因此无法在只允许使用表达式的地方使用它。

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

请参阅RFC中的以下示例:

$ callable = fn()=>抛出新的Exception();

// $ value是不可为空的。
$ value = $ nullableValue?抛出新的InvalidArgumentException();
 
// $ value是真实的。
$ value = $ falsableValue?:抛出新的InvalidArgumentException();

弱地图

弱映射是弱引用键的数据(对象)的集合,这意味着不会阻止对它们的垃圾回收。

PHP 7.4增加了对弱引用的支持,这是一种保留对对象的引用的方式,这种引用不会阻止对象本身被销毁。正如Nikita Popov所指出的,

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

因此,该RFC引入了WeakMap类来创建用作弱映射键的对象,如果没有其他对键对象的引用,则可以将其从弱映射中删除。

在长时间运行的进程中,这将防止内存泄漏并提高性能。请参阅RFC中的以下示例:

$ map =新的WeakMap;
$ obj =新的stdClass;
$地图[$obj] = 42;
var_dump($ map);

使用PHP 8,以上代码将产生以下结果(请参见此处的代码):

object(WeakMap)#1(1){
[0]=>
array(2){
[“key”]=>
object(stdClass)#2(0){
}
[“value”]=>
整数(42)
}
}

如果您取消设置对象,则键会自动从弱贴图中删除:

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

现在的结果如下:

object(WeakMap)#1(0){
}

要仔细查看弱映射,请参阅RFC。该提案获得一致通过。

参数列表中的尾部逗号

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

是否需要为您的网站提供快速,安全且对开发人员友好的托管服务? Kinsta在构建时就考虑了WordPress开发人员,并提供了许多工具和功能强大的仪表板。查看我们的计划

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

Foo类{
公共功能__construct(
字符串$ x,
int $ y,
float $ z,//逗号结尾
){
// 做一点事
}
}

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

在对象上允许:: class语法

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

$ object =新的stdClass;
var_dump($ object :: class); //“ stdClass”
 
$ object = null;
var_dump($ object :: class); // TypeError

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

这项建议获得一致通过。

属性v2

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

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

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

<>
Foo类
{
<>
public const FOO =’foo’;

<>
公开$ x;

<>
公共功能foo(<> $ bar){}
}

$ object =新<> class(){};

<>
函数f1(){}

$ f2 = <>函数(){};

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

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

<>
/ ** docblock * /
<>
函数foo(){}

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

<>
<>
<>
函数foo(){}

请参阅RFC,以更深入地了解PHP属性,用例和替代语法。请注意,属性v2当前正在等待实施。

新的PHP函数

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

str_contains

在PHP 8之前,strstr和strpos是开发人员在给定字符串中搜索针的典型选择。问题是,这两个功能并不是很直观,新PHP开发人员对它们的使用可能会感到困惑。请参见以下示例:

$ mystring =’托管的WordPress托管’;
$ findme =“ WordPress”;
$ pos = strpos($ mystring,$ findme);

如果($ pos!== false){
回显“已找到字符串”;
}其他{
回显“未找到字符串”;
}

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

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

此外,一些框架提供了帮助程序功能来搜索给定字符串内的值(请参阅Laravel帮助程序文档作为示例)。

现在,该RFC建议引入一个新功能,该功能允许在字符串内部进行搜索:str_contains。

str_contains(字符串$ haystack,字符串$ needle):bool

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

因此,感谢str_contains,我们可以编写以下代码:

$ mystring =’托管的WordPress托管’;
$ findme =“ WordPress”;

如果(str_contains($ mystring,$ findme)){
回显“已找到字符串”;
}其他{
回显“未找到字符串”;
}

这更易读,更不容易出错(请参见此处的代码)。
在撰写本文时,str_contains区分大小写,但是将来可能会改变。

str_contains提案以43票对9票获得通过。

str_starts_with()和str_ends_with()

除了str_contains函数之外,还有两个新函数允许在给定字符串中搜索指针:str_starts_with和str_ends_with。

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

str_starts_with(字符串$ haystack,字符串$ needle):bool
str_ends_with(字符串$ haystack,字符串$ needle):bool

如果$ needle比$ haystack长,则两个函数均返回false。

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

“ str_starts_with和str_ends_with功能非常普遍,以至许多主要的PHP框架都支持它,包括Symfony,Laravel,Yii,FuelPHP和Phalcon。”

多亏了他们,我们现在可以避免使用次优且不太直观的功能,例如substr,strpos。这两个函数都区分大小写:

$ str =“ WordPress”;
如果(str_starts_with($ str,“ Word”))回显“ Found!”;

如果(str_starts_with($ str,“ word”))回显“未找到!”;

您可以在此处查看此代码的运行情况。

该RFC已以51到4票获得批准。

get_debug_type

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

这对语言是一个很好的改进,因为gettype()对类型检查没有用。

RFC提供了两个有用的示例,以更好地理解新的get_debug_type()函数和gettype()之间的区别。第一个示例显示了工作中的gettype:

$ bar = [1,2,3];

如果(!($ bar instanceof Foo)){
抛出新的TypeError(’Expected’。Foo :: class。’,got’。(is_object($ bar)?get_class($ bar):gettype($ bar)));
}

在PHP 8中,我们可以使用get_debug_type来代替:

如果(!($ bar instanceof Foo)){
抛出新的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 @ 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在self和parent类型旁边引入了static作为返回类型的用法。
  4. 变量语法调整:此RFC解决了PHP变量语法中的一些残留不一致之处。

摘要

太好了!在本文中,我们介绍了PHP 8发行版中预期的所有关键更改和改进。其中最值得期待的肯定是Just in Time编译器,但是PHP 8附带了更多功能。

请确保将此博客帖子添加为书签,因为我们将在收藏夹被批准后立即将其添加到列表中。 ?

现在轮到您了:您准备好测试即将推出的PHP功能吗?哪一个是你的最爱?在下面的评论部分添加一行。

如果您喜欢这篇文章,那么您会喜欢Kinsta的WordPress托管平台。加速您的网站并获得我们经验丰富的WordPress团队的24/7支持。我们基于Google Cloud的基础架构专注于自动扩展,性能和安全性。让我们向您展示Kinsta的与众不同!查看我们的计划

相关文章