PHP7 升级的那些事儿
掌阅科技 书城研发 高子航
新特性
- 标量类型声明
- 返回值类型声明
- 太空船操作符
- NULL 合并运算符
- 通过 define 定义常量数组
- Unicode codepoint 转译语法
- Session options
标量类型声明
有两种模式: 强制 ( 默认 ) 和 严格模式。 字符串 (string), 整数 (int), 浮点数 (float), 以及布尔值 (bool), 类名,接口,数组。
<?php
function check(int $bool){
var_dump($bool);
}
check(1);
check(true);
返回值类型声明
PHP 7 增加了对返回类型声明的支持。
<?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 的常规比较规则进行的。
<?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 , 它就会返回自身的值,否则返回它的第二个操作数。
<?php
isset($_GET['id']) ? $_GET[id] : 'err'; // PHP5
$_GET['id'] ?? 'err'; // PHP7
通过 define 定义常量数组
<?php
define('ANIMALS', ['dog', 'cat', 'bird']);
echo ANIMALS[1]; // outputs "cat"
intdiv() 两个数相除 取整
<?php
var_dump(intdiv(7, 2));
输出 int(3)
Unicode codepoint 转译语法
这接受一个以 16 进制形式的 Unicode codepoint ,并打印出一个双引号或 heredoc 包围的 UTF-8 编码格式的字符串。 可以接受任何有效的 codepoint ,并且开头的 0 是可以省略的。
<?php
echo "\u{638c}\u{9605}";
?>
旧版输出:\u{638c}\u{9605}
新版输入:掌阅
Session options
现在, session_start() 函数可以接收一个数组作为参数,可以覆盖 php.ini 中 session 的配置项。 比如,把 cache_limiter 设置为私有的,同时在阅读完 session 后立即关闭。
<?php
session_start([
'cookie_lifetime' => 86400,
'read_and_close' => true,
]);
不兼容性
- foreach 不再改变内部数组指针
- 16进制字符串不再被认为是数字
- 被移除的函数
- 移除了 ASP 和 script PHP 标签
- JSON 扩展已经被 JSOND 取代
- INI 文件中 # 注释格式被移除
foreach 不再改变内部数组指针
在 PHP7 之前,当数组通过 foreach 迭代时,数组指针会移动。现在开始,不再如此,见下面代码:
<?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)
十六进制字符串不再被认为是数字
<?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 代码的方式被移除。 受到影响的标签有:
<% %> 、 <%= %> 、 <script language="php"> </script>
ZVAL 结构体优化
PHP5 的 zval 结构体。
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的做法:
/* 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的分配:
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 结构体。
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 类型
/* 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 扩展问题
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 扩展问题
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
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
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
var_dump(mcrypt_encrypt(MCRYPT_DES, "123456789", "123",
MCRYPT_MODE_CBC, "12345678"));