PHP7新特性之抽象语法树(AST)带来的变化详解


Posted in PHP onJuly 17, 2018

本文分析了PHP7新特性之抽象语法树(AST)带来的变化。分享给大家供大家参考,具体如下:

这里大部分内容参照 AST 的 RFC 文档而成:https://wiki.php.net/rfc/abstractsyntaxtree,为了易于理解从源文档中节选部分进行介绍。

本文并不会告诉你抽象语法树是什么,这需要你自己去了解,这里只是描述 AST 给 PHP 带来的一些变化。

新的执行过程

PHP7 的内核中有一个重要的变化是加入了 AST。在 PHP5中,从 php 脚本到 opcodes 的执行的过程是:

  1. Lexing:词法扫描分析,将源文件转换成 token 流;
  2. Parsing:语法分析,在此阶段生成 op arrays。

PHP7 中在语法分析阶段不再直接生成 op arrays,而是先生成 AST,所以过程多了一步:

  1. Lexing:词法扫描分析,将源文件转换成 token 流;
  2. Parsing:语法分析,从 token 流生成抽象语法树;
  3. Compilation:从抽象语法树生成 op arrays。

执行时间和内存消耗

从以上的步骤来看,这比之前的过程还多了一步,所以按常理来说这反而会增加程序的执行时间和内存的使用。但事实上内存的使用确实增加了,但是执行时间上却有所降低。

以下结果是使用小(代码大约 100 行)、中(大约 700 行)、大(大约 2800 行)三个脚本分别进行测试得到的,测试脚本: https://gist.github.com/nikic/289b0c7538b46c2220bc.

每个文件编译 100 次的执行时间(注意文章的测试结果时间是 14 年,PHP7 还叫 PHP-NG 的时候):

php-ng php-ast diff
SMALL 0.180s 0.160s -12.5%
MEDIUM 1.492s 1.268s -17.7%
LARGE 6.703s 5.736s -16.9%

单次编译中的内存峰值:

php-ng php-ast diff
SMALL 378kB 414kB +9.5%
MEDIUM 507kB 643kB +26.8%
LARGE 1084kB 1857kB +71.3%

单次编译的测试结果可能并不能代表实际使用的情况,以下是使用 PhpParser 进行完整项目测试得到的结果:

php-ng php-ast diff
TIME 25.5ms 22.8ms -11.8%
MEMORY 2360kB 2482kB +5.1%

测试表明,使用 AST 之后程序的执行时间整体上大概有 10% 到 15% 的提升,但是内存消耗也有增加,在大文件单次编译中增加明显,但是在整个项目执行过程中并不是很严重的问题。

还有注意的是以上的结果都是在没有 Opcache 的情况下,生产环境中打开 Opcache 的情况下,内存的消耗增加也不是很大的问题。

语义上的改变

如果仅仅是时间上的优化,似乎也不是使用 AST 的充足理由。其实实现 AST 并不是基于时间优化上的考虑,而是为了解决语法上的问题。下面来看一下语义上的一些变化。

yield 不需要括号

在 PHP5 的实现中,如果在一个表达式上下文(例如在一个赋值表达式的右侧)中使用 yield,你必须在 yield 申明两边使用括号:

<?php
$result = yield fn(); // 不合法的
$result = (yield fn()); // 合法的

这种行为仅仅是因为 PHP5 的实现方式的限制,在 PHP7 中,括号不再是必须的了。所以下面这些写法也都是合法的:

<?php
$result = yield;
$result = yield $v;
$result = yield $k => $v;

当然了,还得遵循 yield 的应用场景才行。

括号不影响行为

在 PHP5 中,($foo)['bar'] = 'baz'$foo['bar'] = 'baz' 两个语句的含义不一样。事实上前一种写法是不合法的,你会得到下面这样的错误:

<?php
($foo)['bar'] = 'baz';
# PHP Parse error: Syntax error, unexpected '[' on line 1

但是在 PHP7 中,两种写法表示同样的意思。

同样,如果函数的参数被括号包裹,类型检查存在问题,在 PHP7 中这个问题也得到了解决:

<?php
function func() {
 return [];
}
function byRef(array &$a) {
}
byRef((func()));

以上代码在 PHP5 中不会告警,除非使用 byRef(func()) 的方式调用,但是在 PHP7 中,不管 func() 两边有没有括号都会产生以下错误:

PHP Strict standards:  Only variables should be passed by reference ...

list() 的变化

list 关键字的行为改变了很多。list 给变量赋值的顺序(等号左右同时的顺序)以前是从右至左,现在是从左到右:

<?php
list($array[], $array[], $array[]) = [1, 2, 3];
var_dump($array);
// PHP5: $array = [3, 2, 1]
// PHP7: $array = [1, 2, 3]
# 注意这里的左右的顺序指的是等号左右同时的顺序,
# list($a, $b) = [1, 2] 这种使用中 $a == 1, $b == 2 是没有疑问的。

产生上面变化的原因正是因为在 PHP5 的赋值过程中,3 会最先被填入数组,1 最后,但是现在顺序改变了。

同样的变化还有:

<?php
$a = [1, 2];
list($a, $b) = $a;
// PHP5: $a = 1, $b = 2
// PHP7: $a = 1, $b = null + "Undefined index 1"

这是因为在以前的赋值过程中 $b 先得到 2,然后 $a 的值才变成 1,但是现在 $a 先变成了 1,不再是数组,所以 $b 就成了 null

list 现在只会访问每个偏移量一次:

<?php
list(list($a, $b)) = $array;
// PHP5:
$b = $array[0][1];
$a = $array[0][0];
// PHP7:
// 会产生一个中间变量,得到 $array[0] 的值
$_tmp = $array[0];
$a = $_tmp[0];
$b = $_tmp[1];

空的 list 成员现在是全部禁止的,以前只是在某些情况下:

<?php
list() = $a;   // 不合法
list($b, list()) = $a; // 不合法
foreach ($a as list()) // 不合法 (PHP5 中也不合法)

引用赋值的顺序

引用赋值的顺序在 PHP5 中是从右到左的,现在时从左到右:

<?php
$obj = new stdClass;
$obj->a = &$obj->b;
$obj->b = 1;
var_dump($obj);
// PHP5:
object(stdClass)#1 (2) {
 ["b"] => &int(1)
 ["a"] => &int(1)
}
// PHP7:
object(stdClass)#1 (2) {
 ["a"] => &int(1)
 ["b"] => &int(1)
}

__clone 方法可以直接调用

现在可以直接使用 $obj->__clone() 的写法去调用 __clone 方法。__clone 是之前唯一一个被禁止直接调用的魔术方法,之前你会得到一个这样的错误:

Fatal error: Cannot call __clone() method on objects - use 'clone $obj' instead in ...

变量语法一致性

AST 也解决了一些语法一致性的问题,这些问题是在另外一个 RFC 中被提出的:https://wiki.php.net/rfc/uniform_variable_syntax.

在新的实现上,以前的一些语法表达的含义和现在有些不同,具体的可以参照下面的表格:

Expression PHP5 PHP7
$$foo['bar']['baz'] ${$foo['bar']['baz']} ($$foo)['bar']['baz']
$foo->$bar['baz'] $foo->{$bar['baz']} ($foo->$bar)['baz']
$foo->$bar['baz']() $foo->{$bar['baz']}() ($foo->$bar)['baz']()
Foo::$bar['baz']() Foo::{$bar['baz']}() (Foo::$bar)['baz']()

整体上还是以前的顺序是从右到左,现在从左到右,同时也遵循括号不影响行为的原则。这些复杂的变量写法是在实际开发中需要注意的。

希望本文所述对大家PHP程序设计有所帮助。

PHP 相关文章推荐
PHP 强制性文件下载功能的函数代码(任意文件格式)
May 26 PHP
php设计模式 Delegation(委托模式)
Jun 26 PHP
MySQL连接数超过限制的解决方法
Jul 17 PHP
php动态生成函数示例
Mar 21 PHP
php实现读取超大文件的方法
Jul 28 PHP
基于递归实现的php树形菜单代码
Nov 19 PHP
thinkphp常见路径用法分析
Dec 02 PHP
php创建多级目录的方法
Mar 24 PHP
PHP实现数组array转换成xml的方法
Jul 19 PHP
Yii2创建控制器(createController)方法详解
Jul 23 PHP
thinkPHP内置字符串截取函数用法详解
Nov 15 PHP
ThinkPHP3.2框架自带分页功能实现方法示例
May 13 PHP
阿里云的WindowsServer2016上部署php+apache
Jul 17 #PHP
tp5实现微信小程序多图片上传到服务器功能
Jul 16 #PHP
PHP 爬取网页的主要方法
Jul 13 #PHP
php实现微信发红包功能
Jul 13 #PHP
Yii2框架redis基本应用示例
Jul 13 #PHP
Yii2框架实现登陆添加验证码功能示例
Jul 12 #PHP
Yii框架日志记录Logging操作示例
Jul 12 #PHP
You might like
Ajax PHP 边学边练 之三 数据库
2009/11/26 PHP
PHP实现手机归属地查询API接口实现代码
2012/08/27 PHP
PHP 计算两个特别大的整数实例代码
2018/05/07 PHP
PHP调用接口用post方法传送json数据的实例
2018/05/31 PHP
PHP实现cookie跨域session共享的方法分析
2019/08/23 PHP
Zero Clipboard js+swf实现的复制功能使用方法
2010/03/07 Javascript
JS 实现完美include载入实现代码
2010/08/05 Javascript
jquery 新建的元素事件绑定问题解决方案
2014/06/12 Javascript
随鼠标移动的时钟非常漂亮遗憾的是只支持IE
2014/08/12 Javascript
js实例属性和原型属性示例详解
2014/11/23 Javascript
浅谈javascript 函数内部属性
2015/01/21 Javascript
js正则表达式匹配数字字母下划线等
2015/04/14 Javascript
javascript跑马灯抽奖实例讲解
2020/04/17 Javascript
JavaScript闭包和范围实例详解
2016/12/19 Javascript
angularjs实现上拉加载和下拉刷新数据功能
2017/06/12 Javascript
vue.js的手脚架vue-cli项目搭建的步骤
2017/08/30 Javascript
JQuery Ajax动态加载Table数据的实例讲解
2018/08/09 jQuery
Vue加载组件、动态加载组件的几种方式
2018/08/31 Javascript
react 组件传值的三种方法
2019/06/03 Javascript
vue自定义指令实现仅支持输入数字和浮点型的示例
2019/10/30 Javascript
[41:21]夜魇凡尔赛茶话会 第三期02:看图识人
2021/03/11 DOTA
Numpy中转置transpose、T和swapaxes的实例讲解
2018/04/17 Python
华为2019校招笔试题之处理字符串(python版)
2019/06/25 Python
django-初始配置(纯手写)详解
2019/07/30 Python
Python数据存储之 h5py详解
2019/12/26 Python
Python中url标签使用知识点总结
2020/01/16 Python
基于python实现微信好友数据分析(简单)
2020/02/16 Python
python 元组的使用方法
2020/06/09 Python
Python 列表推导式需要注意的地方
2020/10/23 Python
python实现图片转字符画的完整代码
2021/02/21 Python
马来西亚领先的在线礼品店:Giftr
2018/08/23 全球购物
市政工程技术专业自荐书
2014/07/06 职场文书
优秀毕业生的求职信
2014/07/21 职场文书
员工手册编写范本
2015/05/14 职场文书
教你用python控制安卓手机
2021/05/13 Python
进行数据处理的6个 Python 代码块分享
2022/04/06 Python