PHP7 升级的那些事儿

原创
2017/01/06 16:49
阅读数 260

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-src/ext/mcrypt/mcrypt.c

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

Thanks

展开阅读全文
打赏
0
1 收藏
分享
加载中
更多评论
打赏
0 评论
1 收藏
0
分享
返回顶部
顶部