我在公司的生產環境已經升級了PHP7,大部分活躍的擴展均可以兼容PHP7或者有了PHP7的分支,可是處理protocolbuffers數據的擴展一直沒有人來升級,我只能摸着石頭過河,本身作一把升級了。目前已經編譯經過,處在處理單測失敗的case階段,歡迎你們一塊兒討論。對PHP擴展升級能夠先看一下官方的升級說明,https://wiki.php.net/phpng-upgrading,基本能夠應對大部分場景,剩下的就須要本身讀源代碼了。php
zval結構體是Zend內核的很是核心的結構,在PHP5和PHP7之間的差異很是大,我給出2處文章供你們學習,基本上能夠表明這塊知識點最權威的介紹了。html
深刻理解PHP7之zval(鳥哥)
https://github.com/laruence/php7-internal/blob/master/zval.mdgit
變量在 PHP7 內部的實現(Nikita Popov)中文版
http://0x1.im/blog/php/Internal-value-representation-in-PHP-7-part-1.html
http://0x1.im/blog/php/Internal-value-representation-in-PHP-7-part-2.htmlgithub
PHP7再也不使用zval的二級指針,大多數場景下出現的zval*變量都改爲zval,相應的使用在這些變量上的宏Z_PP也須要改爲Z_P。
在大部分場景下,PHP7是在棧上直接使用zval,不須要去堆上分配內存。這時,zval 就須要改爲zval,宏也須要從Z__P改爲Z_,建立宏從ZVAL_(var)轉換成ZVAL_*(&var)。因此,分配zval內存的宏php7
ALLOC_ZVAL、ALLOC_INIT_ZVAL、MAKE_STD_ZVAL都被刪掉了。 - zval *zv; - MAKE_STD_ZVAL(zv); - array_init(zv); + zval zv; + array_init(&zv);
PHP7中zval的long和double類型是不須要引用計數的,因此相關的宏要作調整。學習
- Z_ADDREF_P(zv) + Z_TRY_ADDREF_P(zv);
PHP7中zval的類型,刪除了IS_BOOL,增長了IS_TRUE和IS_FALSE。ui
- if (Z_TYPE_P(zv) == IS_BOOL) { - } + if (Z_TYPE_P(zv) == IS_TRUE) { + } else if (Z_TYPE_P(zv) == IS_FALSE) { + }
PHP7中增長了一個新的內置字符串類型zend_string,下面是Zend內核中的結構體定義。.net
struct _zend_string { zend_refcounted_h gc; /* 垃圾回收結構體 */ zend_ulong h; /* 字符串哈希值 */ size_t len; /* 字符串長度 */ char val[1]; /* 字符串內容 */ };
gc是PHP7中的全部非標量結構都包含的垃圾回收結構體變量;h是字符串哈希值,做爲HashTable的key時不須要每次都從新計算哈希值,提升了效率;len是字符串長度,同理每次使用到字符串的長度時不須要再計算,提升了效率;val[1]是C語言的黑科技,此處按照char *理解便可。這裏有三個宏幫助咱們方便的使用zend_string的變量。設計
#define ZSTR_VAL(zstr) (zstr)->val #define ZSTR_LEN(zstr) (zstr)->len #define ZSTR_H(zstr) (zstr)->h
建立和銷燬zend_string使用如下方法。指針
zend_string *zend_string_init(const char *str, size_t len, int persistent) void zend_string_release(zend_string *s)
zend_string用來替代PHP5中使用char *和int的場景,尤爲是不少API的參數和返回值都作了調整。
- int zend_hash_find(const HashTable *ht, const char *arKey, uint nKeyLength, void **pData) + zval* ZEND_FASTCALL zend_hash_find(const HashTable *ht, zend_string *key) - void zend_mangle_property_name(char **dest, int *dest_length, const char *src1, int src1_length, const char *src2, int src2_length, int internal); + zend_string *zend_mangle_property_name(const char *src1, size_t src1_length, const char *src2, size_t src2_length, int internal)
在PHP7中使用HashTable的API方法時,有了很是明顯的變化。
查詢方法,PHP5使用引用傳參的方式,同時返回SUCCESS/FAILURE;PHP7直接返回結果,查詢無結果時返回NULL。
- int zend_hash_find(const HashTable *ht, const char *arKey, uint nKeyLength, void **pData) + zval* ZEND_FASTCALL zend_hash_find(const HashTable *ht, zend_string *key)
HashTable的API方法中的key,PHP5中使用char 和int表明的字符串;PHP7中使用zend_string表明的字符串,同時提供了對char 和int支持的一組方法,可是須要注意的是這裏的字符串長度是不包括結尾的'0'的,在升級擴展時不免會碰到不少地方須要加減一。
- int zend_hash_exists(const HashTable *ht, const char *arKey, uint nKeyLength) + zend_bool zend_hash_exists(const HashTable *ht, zend_string *key) + zend_bool zend_hash_str_exists(const HashTable *ht, const char *str, size_t len)
PHP7爲HashTable的value爲指針時設計了一組API,在常規的API方法後添加後綴_ptr便可。
void *zend_hash_find_ptr(const HashTable *ht, zend_string *key) void *zend_hash_update_ptr(HashTable *ht, zend_string *key, void *pData)
PHP7爲HashTable的輪詢設計了一組宏,使用起來很是方便。
ZEND_HASH_FOREACH_VAL(ht, val) ZEND_HASH_FOREACH_KEY(ht, h, key) ZEND_HASH_FOREACH_PTR(ht, ptr) ZEND_HASH_FOREACH_NUM_KEY(ht, h) ZEND_HASH_FOREACH_STR_KEY(ht, key) ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, val) ZEND_HASH_FOREACH_KEY_VAL(ht, h, key, val)
這裏有點複雜,我直接附上個人代碼,結合代碼來作詳細說明。
typedef struct{ int max; int offset; zend_object zo; } php_protocolbuffers_unknown_field_set; static zend_object_handlers php_protocolbuffers_unknown_field_set_object_handlers; static void php_protocolbuffers_unknown_field_set_free_storage(php_protocolbuffers_unknown_field_set *object TSRMLS_DC) { php_protocolbuffers_unknown_field_set *unknown_field_set; unknown_field_set = (php_protocolbuffers_unknown_field_set*)((char *) object - XtOffsetOf(php_protocolbuffers_unknown_field_set, zo)); zend_object_std_dtor(&unknown_field_set->zo TSRMLS_CC); } zend_object *php_protocolbuffers_unknown_field_set_new(zend_class_entry *ce TSRMLS_DC) { php_protocolbuffers_unknown_field_set *intern; intern = ecalloc(1, sizeof(php_protocolbuffers_unknown_field_set) + zend_object_properties_size(ce)); zend_object_std_init(&intern->zo, ce); object_properties_init(&intern->zo, ce); intern->zo.handlers = &php_protocolbuffers_unknown_field_set_object_handlers; intern->max = 0; intern->offset = 0; return &intern->zo; } void php_protocolbuffers_unknown_field_set_class(TSRMLS_D) { // 此處有省略 php_protocol_buffers_unknown_field_set_class_entry->create_object = php_protocolbuffers_unknown_field_set_new; memcpy(&php_protocolbuffers_unknown_field_set_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); php_protocolbuffers_unknown_field_set_object_handlers.offset = XtOffsetOf(php_protocolbuffers_unknown_field_set, zo); php_protocolbuffers_unknown_field_set_object_handlers.free_obj = php_protocolbuffers_unknown_field_set_free_storage; }
咱們想自定義一個php_protocolbuffers_unknown_field_set的對象,在它的結構體裏面除了zend_object,還有自定義的max和offset,務必把zend_object放在最後。
實際生成對象的地方基本就是標準寫法,先分配內存,包括php_protocolbuffers_unknown_field_set結構體的內存和對象屬性的內存;而後對zend_object的handlers賦值;最後再對本身自定義的變量初始化。
實際生成對象handler的地方也是標準寫法,先分配內存,offset是必須設置的,可選的設置項有free_obj,dtor_obj,clone_obj。
想取到zend_object,須要(STRUCT_NAME )((char )OBJECT - XtOffsetOf(STRUCT_NAME, zo))