新特性

  • 标量类型声明
  • 返回值类型声明
  • 太空船操作符
  • NULL 合并运算符
  • 通过 define 定义常量数组
  • Unicode codepoint 转译语法
  • Session options

标量类型声明

有两种模式: 强制 ( 默认 ) 和 严格模式。
字符串 (string), 整数 (int), 浮点数 (float), 以及布尔值 (bool), 类名,接口,数组。

1
2
3
4
5
6
7
8
9
<?php
function check(int $bool){
var_dump($bool);
}
check(1);
check(true);

返回值类型声明

PHP 7 增加了对返回类型声明的支持。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
function arraysSum(array ...$arrays): array {
return array_map(function(array $array): int {
return array_sum($array);
}, $arrays);
}
print_r(arraysSum([1,2,3], [4,5,6], [7,8,9]));
Array(
[0] => 6
[1] => 15
[2] => 24
)

太空船操作符

太空船操作符用于比较两个表达式。当 $a 大于、等于或小于 $b 时它分别返回 -1 、 0 或 1 。 比较的原则是沿用 PHP 的常规比较规则进行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
// Integers
echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1
// Floats
echo 1.5 <=> 1.5; // 0
echo 1.5 <=> 2.5; // -1
echo 2.5 <=> 1.5; // 1
// Strings
echo "a" <=> "a"; // 0
echo "a" <=> "b"; // -1
echo "b" <=> "a"; // 1

NULL 合并运算符

项目中存在大量同时使用三元表达式和 isset() 的情况,新增了 null 合并运算符 (??) 这个语法糖。如果变量存在且值不为 NULL , 它就会返回自身的值,否则返回它的第二个操作数。

1
2
3
4
5
<?php
isset($_GET['id']) ? $_GET[id] : 'err'; // PHP5
$_GET['id'] ?? 'err'; // PHP7

通过 define 定义常量数组

1
2
3
4
5
<?php
define('ANIMALS', ['dog', 'cat', 'bird']);
echo ANIMALS[1]; // outputs "cat"

intdiv() 两个数相除 取整

1
2
3
4
5
<?php
var_dump(intdiv(7, 2));
输出 int(3)

Unicode codepoint 转译语法

这接受一个以 16 进制形式的 Unicode codepoint ,并打印出一个双引号或 heredoc 包围的 UTF-8 编码格式的字符串。 可以接受任何有效的 codepoint ,并且开头的 0 是可以省略的。

1
2
3
4
5
6
7
8
<?php
echo "\u{638c}\u{9605}";
?>
旧版输出:\u{638c}\u{9605}
新版输入:掌阅

Session options

现在, session_start() 函数可以接收一个数组作为参数,可以覆盖 php.ini 中 session 的配置项。
比如,把 cache_limiter 设置为私有的,同时在阅读完 session 后立即关闭。

1
2
3
4
5
6
<?php
session_start([
'cookie_lifetime' => 86400,
'read_and_close' => true,
]);

不兼容性

  • foreach 不再改变内部数组指针
  • 16进制字符串不再被认为是数字
  • 被移除的函数
  • 移除了 ASP 和 script PHP 标签
  • JSON 扩展已经被 JSOND 取代
  • INI 文件中 # 注释格式被移除

foreach 不再改变内部数组指针

在 PHP7 之前,当数组通过 foreach 迭代时,数组指针会移动。现在开始,不再如此,见下面代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
$array = [0, 1, 2];
foreach ($array as &$val) {
var_dump(current($array));
}
?>
PHP5 输出:
int(1)
int(2)
bool(false)
PHP7 输出:
int(0)
int(0)
int(0)

十六进制字符串不再被认为是数字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
var_dump("0x123" == "291");
var_dump(is_numeric("0x123"));
var_dump("0xe" + "0x1");
var_dump(substr("foo", "0x1"));
?>
PHP5 输出:
bool(true)
bool(true)
int(15)
string(2) "oo"
PHP7 输出:
bool(false)
bool(false)
int(0)
Notice: A non well formed numeric value encountered in /tmp/test.php on line 5
string(3) "foo"

PHP7 中被移除的函数

  • 已废弃的 mcrypt_generic_end() 函数已被移除,请使用 mcrypt_generic_deinit() 代替
  • mcrypt_*
    • mcrypt_ecb
    • mcrypt_cbc
    • mcrypt_cfb
    • mcrypt_ofb
  • dl() 在 PHP-FPM 不再可用,在 CLI 和 embed SAPIs 中仍可用。
  • 在配置文件 php.ini 中, always_populate_raw_post_data 、 asp_tags 、 xsl.security_prefs 被移除了。

移除了 ASP 和 script PHP 标签

使用类似 ASP 的标签,以及 script 标签来区分 PHP 代码的方式被移除。 受到影响的标签有:

1
<% %><%= %><script language="php"> </script>

ZVAL 结构体优化

PHP5 的 zval 结构体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct _zval_struct {
union {
long lval;
double dval;
struct {
char *val;
int len;
} str;
HashTable *ht;
zend_object_value obj;
zend_ast *ast;
} value;
zend_uint refcount__gc;
zend_uchar type;
zend_uchar is_ref__gc;
};

存在问题

  • 这个结构体的大小是(在64位系统)24个字节
  • 没有预留任何的自定义字段

5.3的时候新引入专门解决循环引用的GC, 它不得采用如下的比较hack的做法:

1
2
3
4
5
6
7
/* The following macroses override macroses from zend_alloc.h */
#undef ALLOC_ZVAL
#define ALLOC_ZVAL(z) \
do { \
(z) = (zval*)emalloc(sizeof(zval_gc_info)); \
GC_ZVAL_INIT(z); \
} while (0)

它用zval_gc_info劫持了zval的分配:

1
2
3
4
5
6
7
typedef struct _zval_gc_info {
zval z;
union {
gc_root_buffer *buffered;
struct _zval_gc_info *next;
} u;
} zval_gc_info;

实际上来说我们在PHP5时代申请一个zval其实真正的是分配了32个字节。

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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
struct _zval_struct {
union {
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;
void *ptr;
zend_class_entry *ce;
zend_function *func;
struct {
uint32_t w1;
uint32_t w2;
} ww;
} 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 var_flags;
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 */
} u2;
};

这个新的zval在64位环境下,现在只需要16个字节(2个指针size)。

PHP7 类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* regular data types */
#define IS_UNDEF 0
#define IS_NULL 1
#define IS_FALSE 2
#define IS_TRUE 3
#define IS_LONG 4
#define IS_DOUBLE 5
#define IS_STRING 6
#define IS_ARRAY 7
#define IS_OBJECT 8
#define IS_RESOURCE 9
#define IS_REFERENCE 10
/* constant expressions */
#define IS_CONSTANT 11
#define IS_CONSTANT_AST 12
/* fake types */
#define _IS_BOOL 13
#define IS_CALLABLE 14
/* internal types */
#define IS_INDIRECT 15
#define IS_PTR 17

IS_BOOL -> IS_FALSE, IS_TRUE

不再进行引用计数的类型:

IS_LONG
IS_DOUBLE

IS_NULL
IS_FALSE
IS_TRUE

升级遇到的问题

  • memcahced 扩展问题
    • libmemcahced 库版本依赖 >= 1.0.18
  • qconf 扩展问题
  • redis扩展
  • mcrypt 不兼容问题
  • 使用 php7cc 检查整体代码的兼容性

qconf 扩展问题

1
2
3
4
5
6
7
8
9
10
11
12
PHP_FUNCTION(test_bug)
{
char *path, *idc;
int path_len, idc_len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
"s|s", &path, &path_len, &idc, &idc_len) == FAILURE) {
return;
}
RETVAL_STRINGL(path, path_len);
}

redis 扩展问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void add_class_constants(...) {
............................
#ifdef PHP_SESSION
php_session_register_module(&ps_mod_redis);
php_session_register_module(&ps_mod_redis_cluster);
#endif
}
PHP_MINIT_FUNCTION(redis)
{
............................
............................
/* Add shared class constants to Redis and RedisCluster objects */
add_class_constants(redis_ce, 0 TSRMLS_CC);
add_class_constants(redis_cluster_ce, 1 TSRMLS_CC);
return SUCCESS;
}

mcrypt

PHP5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void php_mcrypt_do_crypt(...) /* {{{ */
{
..........................
if (count == 0 && key_length_sizes == NULL) { /* all lengths 1 - k_l_s = OK */
..........................
} else if (count == 1) { /* only m_k_l = OK */
..........................
} else { /* dertermine smallest supported key > length of requested key */
use_key_length = max_key_length; /* start with max key length */
..........................
key_s = emalloc(use_key_length);
memset(key_s, 0, use_key_length);
memcpy(key_s, key, MIN(key_len, use_key_length));
}
..........................

PHP7

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void php_mcrypt_do_crypt(...) /* {{{ */
{
..........................
if (php_mcrypt_ensure_valid_key_size(td, (int)key_len) == FAILURE {
mcrypt_module_close(td);
RETURN_FALSE;
}
if (php_mcrypt_ensure_valid_iv(td, iv, (int)iv_len) == FAILURE) {
mcrypt_module_close(td);
RETURN_FALSE;
}
..........................

php-src/ext/mcrypt/mcrypt.c

1
2
3
<?php
var_dump(mcrypt_encrypt(MCRYPT_DES, "123456789", "123",
MCRYPT_MODE_CBC, "12345678"));