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