PHP5和PHP7的差异(扩展部分)

PHP5用的extension在PHP7上不能运行而不修改。根据我的调查,许多extension似乎是在另一个分支上进行开发(例如APCu、msgpack、memcached等)。在使用#if进行分支条件判断并实现双向兼容也是一项艰巨的任务。

我写了一个实验性的PHP7扩展“php7_explorer”。在写的过程中,我再次感受到了与PHP5扩展的不同之处,所以我将大致总结一下这些区别。

不再需要写”おまじない”了。

在PHP5之前,需要在Zend API的原型声明的最后添加TSRMLS_DC,并在调用时参数列表的最后添加TSRMLS_CC,这是一个所谓的”咒语”。这是为了支持ZTS(Zend Thread Safety),只在需要的时候才传递额外的参数的宏定义。

在PHP7的改进中变得不再需要。因此,在PHP7的扩展中可以更简洁地编写。当然,如果是PHP5/7兼容的扩展,按照以往的方式编写也能正常运行。

只有函数参数列表中的“咒语”不再存在,ZTS支持本身仍然存在。此外,还需要注意其他地方的“咒语”可能有些变化。

现在我习以为常的型号已经消失了。

zend_uint 廃止 → uint32_t を使うように

然而,其中的zend_ulong等项却保留了下来,让人感到不满。

当在使用zend_parse_parameters()函数解析字符串参数时,类型发生了变化。

当在zend_parse_parameters()的第二个参数中指定为”s”时,可以解释PHP函数的字符串类型参数并将其转换为C世界的变量值。

然而,在PHP5中,我们通过传递char **和int *来获取字符串的值,并将其填充为所需的值。但是从PHP7开始,这种方式已更改为char **和size_t *。由于int和size_t在64位环境下具有不同的大小,如果不注意使用,可能会导致意想不到的错误。请务必谨慎使用。

这个文字串能否转化为zend_string *。

在某些API中,const char *str, size_t str_len这两个参数会变成zend_string *str的一个参数。而在另一个API中,仍然保持原来的两个参数。我不知道在何种情况下会发生这种变化。

关于字符串的长度,统一规定不包括最后的空字符。

在一部API中,char *字符串的长度计算方式发生了变化。例如,PHP5时代时我们可以使用strlen函数来计算长度。

add_assoc_bool_ex(&zv, "foo", sizeof("foo"), 0);

从PHP7开始,这被写成

add_assoc_bool_ex(&zv, "foo", sizeof("foo") - 1, 0);

在中国,您可以这样来转述这段话:PHP5和PHP7使用相同的原型声明,但参数的意义却有所不同,这真是令人恶心。我一不小心就会生气地大幅修改代码,以使用zend_string。

指针可能会减少两个级别,也可能减少一个级别,或者不减少。

在PHP7中,zval的默认传递方式从指针传递改为值传递,导致各种Zend API的参数发生了重大变化。因此,需要重新编写大部分API调用部分。

作为一个例子,让我们看一下调用用户函数的API call_user_function_ex()。

ZEND_API int call_user_function_ex(HashTable *function_table,
                                   zval **object_pp,
                                   zval *function_name,
                                   zval **retval_ptr_ptr,
                                   zend_uint param_count,
                                   zval **params[],
                                   int no_separation,
                                   HashTable *symbol_table TSRMLS_DC);

在PHP7中,它已经改变为以下方式。

ZEND_API int call_user_function_ex(HashTable *function_table,
                                   zval *object,
                                   zval *function_name,
                                   zval *retval_ptr,
                                   uint32_t param_count,
                                   zval params[],
                                   int no_separation,
                                   zend_array *symbol_table);

這個樣本相當可怕。第二和第四個參數的 zval ** 經過一次指針減少變成 zval *,而第三個參數則保持為 zval *。然而,第六個參數則從 zval *** 變成了 zval *。其實,我自己也不太有把握,但我想應該是正確的。

zend_hash_*() 系列的API经历了相当大的变化。

虽然已经介绍过相似的内容,但是大多数哈希操作系的 API 在参数的数量和类型上已经发生了变化,如果想将 PHP5 的扩展进行 PHP7 兼容性适配,会感觉需要花费相当大的功夫。例如,zend_hash_find() 在参数中传递了 zval *** 并接收值,但现在它作为函数返回值返回了 zval *,超出了能够通过 #if 切换的限度,给人留下了这样的印象。

另外,直到PHP5为止,内部哈希表是通过管理指针来进行操作的,将其解释为zval *和其他指针。但是,在PHP7中,开始管理zval。为了在处理除zval之外的指针值时使用,新的API被创建,并加上了_ptr后缀,例如zend_hash_find_ptr()。

此外,还引入了类似于ZEND_HASH_FOREACH_VAL(ht, val)的hash系宏函数。顾名思义,这是一个用于对哈希的所有元素执行操作的宏函数,非常方便实用,希望能够将其回溯至PHP5系列。

创建与自定义类相对应的结构体的方式发生了改变。

当使用extension来创建类时,在PHP5中通常会创建一个结构体,其中将zend_object放在开头,如下所示。

struct custom_object {
   zend_object std;
   void  *custom_data;
}

对此,在PHP7中,需要将zend_object放置在末尾,如下所示。

struct custom_object {
   void  *custom_data;
   zend_object std;
}

对应于这个结构体的内存分配方式也发生了变化,并且变成了下面这样的代码。

     struct custom_object *intern = ecalloc(1, 
         sizeof(struct custom_object) + 
         zend_object_properties_size(ce));

这是一个用于将zend_object的properties_table变成可变个数的技巧。

可能还有其他对象生成函数的返回类型发生变化,可能会有一些棘手的地方需要注意。

请参考

    • Upgrading PHP extensions from PHP5 to NG

 

    PHP RFC: Native TLS
广告
将在 10 秒后关闭
bannerAds