编译前的php目录
我们从官网下载php文件解压后,讲解几个重点的文件夹
Zend:php核心内核放在zend文件夹
sapi:这是 PHP 内核提供给外部调用其服务的接口,即外部系统可以通过 SAPI 来调用 PHP 提供的编译脚本、执行脚本的服务。PHP 中实现的 SAPI 有很多,Cli、Fpm 是我们比较常见的。
从下图可以较为清晰的理解外部系统是如何通过 SAPI 调用 PHP 服务的
ext:这个大家比较熟悉这是放置扩展的地方。
configure: 这个文件主要负责编译工作
可以通过
1 2 3 4 5 6 |
./configure -h > test vim test //查看配置 注释: ./configure --prefix=安装目录 --enable-fpm开启fpm --enable-debug开启debug,不开启会对性能有优化 ./configure --prefix=/home/codes/php/php7.1.0/output --enable-fpm --enable-debug make //编译时间比较长 make install //安装 |
安装php完成了。
你可以从make输出的编译过程中找到
开启–enable-debug 等于 -o0 默认不开启-o2,不开启会对性能有优化。
安装完成后的php目录
sbin: 这里面放着php-fpm,因为编译时使用了pfm方式,所以才会有,如果没有开启就不会存在了。
bin: 有个php文件,就是php的可执行程序
PHP7和PHP5的性能对比
来到php7未编译源码的Zend目录下,bench.php里面有很多的方法,都是用来测试性能的测试函数和方法。
我们用php7和php5都来运行这个文件。
/Zend/bench.php
测试php7:0.774秒,php5:2.056秒
结论:相差2倍
还有一个测试文件/Zend/micro_bench.php (更详细的测试,有类对象的性能)
php7: 3.8秒 | php5 : 9秒
php7新特性
太空船操作符 <=>
用于比较两个表达式,例如当$a小于、等于或者大于$b 时,他分别返回 -1、0、1
echo 1 <=> 1 //0
echo 2 <=> 1 //1
echo 1 <=>2 //-1
语法是这样的:$c = $a <=> $b;
这句代码的意思是
- 如果$a > $b, $c 的值为1
- 如果$a == $b, $c 的值为0
- 如果$a < $b, $c 的值为-1
在没有太空船运算符的时候,我们只能这样写代码
$c = $a > $b ? 1 : ( $a==$b ? 0 : -1 );
或者用if else条件语句写得更多,
类型声明
修改 declare(strict_type=1); //strict_type=1 表示开启严格模式,也就是强制类型模式
1 2 3 4 5 6 7 |
function sum(int ...$ints):int{ return array_sum($ints) } var_dump(sum(1,'2','3.1',4.1)); |
执行结果
1 |
Fatal error: Uncaught TypeError: Argument 2 passed to sum() must be of the type integer, string given |
如果关闭严格模式,传入的参数会先转换为Int,结果为10
null合并操作符 ??
1 2 3 4 5 6 7 |
//php5 $page=isset($_GET['p'])?$_GET['p']:0; //php7 $page =$_GET['p'] ??0; //三元运算符 $page =$_GET['p'] ?? $_POST['p'] ??0; |
常量数组
php7之前无法通过define来定义一个常量数组的,php7支持了这个操作
1 2 |
define('STUDENT',['boy','girl']); print_r(STUDENT); |
namespace批量导入
1 2 3 4 5 6 7 |
//php之前 use Space\ClassA; use Space\ClassB; use Space\ClassC C; //php7 use Space\{ClassA,ClassB,ClassC as C}; |
throwable接口
这个用来显示异常的
php5 报错,php7就可以捕获了
1 2 3 4 5 |
try{ undefindfunc(); }catch(Error $e){ var_dump($e); } |
下面方法也是:
1 2 3 4 |
set_exception_handler(function($e){ var_dump($e); }); undefindfunc(); |
- 在php7之前,如果代码中有语法错误或者fatal error时,程序会直接报错退出,php7中实现了全局throwable接口,原来的Exception和部分Error实现了该接口
- 这种Error可以像Exception一样被第一个匹配的try/catch块捕获。如果没有匹配的catch块,则调用异常函数处理。如果尚未注册异常处理函数,则按照传统方式处理(Fatal error)
- Error类并非继承自Exception,所以不能用catch(Exception e)来捕获,可用catch(Errore)来捕获,可用catch(Errore){} ,或者通过注册异常处理函数(set_exception_handler())来捕获Error
Closure::call()
在php7之前,当动态的给一个对象添加方法时,可以通过Closure来复制一个闭包对象,并绑定到一个$this对象和类作用域
1 2 3 4 5 6 7 8 9 |
class People{ private $age=10; } $f=function(){ return $this->age+1; }; $p=$f->bindTo(new People,'People'); echo $p(); |
在php7可通过call来暂时绑定一个闭包对象到$this对象并调用它
1 2 3 4 5 6 7 8 |
class People{ private $age=10; } $f=function(){ return $this->age+1; }; echo $f->call(new People); |
intdiv
1 |
intdiv(10,3) |
等于3 ,取整数
list的方括号写法
1 2 3 4 5 6 |
//php5 $a=[1,2,3]; list($n1,$n2,$n3)=$a; //php7 [$n1,$n2,$n3]=[4,5,6]; //[]并不是数组,而是list的简略形式 |
抽象语法树(AST)
1 2 |
//在php5里是报错的 ($a)['b'] = 1; |
在php7里
返回 array(1){[“b”]=>int(1)}
…是php的一个语法糖
php5.6的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?php function add($a, $b, $c) { var_dump($a); var_dump($b); var_dump($c); echo "<br>"; return $a + $b + $c; } $num=[2, 3]; echo add(1, ...$num); echo "<hr>"; function sum(int ...$ints): int{ var_dump($ints); echo "<br>"; return array_sum($ints); } var_dump(sum(1,'2','3.1',4.1)); |
输出:
1 2 3 4 |
int(1) int(2) int(3) 6 array(4) { [0]=> int(1) [1]=> int(2) [2]=> int(3) [3]=> int(4) } int(10) |
也就是说第一个函数,…作为参数,就是把数组打散传入的方法里。
第二个函数,方法设置的参数使用… 会把零散的数字,组合成数组。
也就是说…ints和…$num就是一个数组,但是会根据情况对号入座
PHP7结构体
php7中的zval结构体对比php5优化了很多,一个zval只占用16字节的空间,php7中的zval的结构体如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
struct _zval_struct { zend_value value; /* value */ union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar type, /* active type */ zend_uchar type_flags, zend_uchar const_flags, zend_uchar reserved) /* call info for EX(This) */ } v; uint32_t type_info; } u1; union { uint32_t next; /* hash collision chain */ uint32_t cache_slot; /* literal cache slot */ uint32_t lineno; /* line number (for ast nodes) */ uint32_t num_args; /* arguments number for EX(This) */ uint32_t fe_pos; /* foreach position */ uint32_t fe_iter_idx; /* foreach iterator index */ uint32_t access_flags; /* class constant access flags */ uint32_t property_guard; /* single property guard */ uint32_t extra; /* not further specified */ } u2; }; |
value 8个字节, union u1占用四个字节,union u2占用也是四个字节 ,总共8个字节
zval用来表示php里的任意变量
1 2 3 4 5 6 7 8 9 10 11 |
union { uint32_t next; /* hash collision chain 用来解决数组中hash冲突的*/ uint32_t cache_slot; /* literal cache slot 运行池缓存的*/ uint32_t lineno; /* line number (for ast nodes) 标记php行号,一般用在抽象语法树上 */ uint32_t num_args; /* arguments number for EX(This) 调用传入参数个数*/ uint32_t fe_pos; /* foreach position foreach的位置 */ uint32_t fe_iter_idx; /* foreach iterator index foreach的游标的位置 */ uint32_t access_flags; /* class constant access flags 类中使用,类型标记*/ uint32_t property_guard; /* single property guard 防止魔术方法循环引用,在_set和_get中使用*/ uint32_t extra; /* not further specified */ } u2; |
value的结构体占用了8个字节,这里面存储的就是zval中的value值的地方。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
typedef union _zend_value { zend_long lval; /* long value */ //整形 double dval; /* double value */ //浮点型 zend_refcounted *counted; //引用计数 zend_string *str; //字符串类型 zend_array *arr; //数组类型 zend_object *obj; //对象类型 zend_resource *res; //资源类型 zend_reference *ref; //引用类型 zend_ast_ref *ast; //抽象语法树 zval *zv; //zval类型 void *ptr; //指针类型 zend_class_entry *ce; //class类型 zend_function *func; //funciton类型 struct { uint32_t w1; uint32_t w2; } ww; } zend_value; |
上面的代码在Zend/zend_types.h
虽然我们写的php代码是弱类型,但是在底层实现还是要区分类型的。
那么如何区分我们是什么类型的呢?
1 2 3 4 5 6 7 8 9 10 |
union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar type, /* active type */ zend_uchar type_flags, zend_uchar const_flags, zend_uchar reserved) /* call info for EX(This) */ } v; uint32_t type_info; } u1; |
zend_uchar type, 就是类型,包含如下类型
zend_uchar type_flags, 变量类型特有的标记
他表示常量不可变的类型
第一个常量类型,第二个不可变类型(共享内存的数组),第三个引用计数的类型,第四个包含循环引用的类型,第四个可被复制的类型
zend_uchar const_flags, 常量类型特有的标记
zend_uchar reserved 保留字段
总结:
_zend_value使用哪个类型,是由 _zval_struct中的 zend_uchar type决定的
例如:_zval_struct中的 zend_uchar type 是IS_LONG整形,我们直接取_zend_value里的*lval就得到值了;
_zval_struct中的 zend_uchar type 是IS_STRING整形,我们直接取_zend_value里的*str就得到值了;
调试测试一下
准备zval.php
1 2 |
$a = 2 echo $a; |
gdb ../../php-7.1.0-debug/output/bin/php //启动gdb调试
b ZEND_ECHO_SPEC_CV_HANDLER //在函数加断点
r zval.php //运行程序
n是进入下一行的意思,不会进入函数体。
p输出(print)变量”var”的值
你会看到type = 4,获取值lval = 2