如何把擴展從PHP5升級到PHP7

我在公司的生產環境已經升級了PHP7,大部分活躍的擴展均可以兼容PHP7或者有了PHP7的分支,可是處理protocolbuffers數據的擴展一直沒有人來升級,我只能摸着石頭過河,本身作一把升級了。目前已經編譯經過,處在處理單測失敗的case階段,歡迎你們一塊兒討論。對PHP擴展升級能夠先看一下官方的升級說明,https://wiki.php.net/phpng-upgrading,基本能夠應對大部分場景,剩下的就須要本身讀源代碼了。php

zval

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) {
+ }

zend_string

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)

HashTable API

在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))

相關文章
相關標籤/搜索