常见的结构(Common Structures)¶

    框架中最常用的变量是 zval。一个PHP应用程序中的每个值是一个变量。zval 是一个多态性结构,即一个 zval 变量可以是任何值(bool、string、long、double、array等)。浏览 Phalcon7 内部的一些代码,你会看到,大多数变量的声明 zval。PHP具有标量数据类型和非标量数据类型(array 和 object)。

    PHP7中的zval的类型做了比较大的调整, 总体来说有如下17种类型:

    作用于zval的有:

    1. #define IS_TYPE_CONSTANT //是常量类型
    2. #define IS_TYPE_IMMUTABLE //不可变的类型, 比如存在共享内存的数组
    3. #define IS_TYPE_REFCOUNTED //需要引用计数的类型
    4. #define IS_TYPE_COLLECTABLE //可能包含循环引用的类型(IS_ARRAY, IS_OBJECT)
    5. #define IS_TYPE_COPYABLE //可被复制的类型, 还记得我之前讲的对象和资源的例外么? 对象和资源就不是
    6. #define IS_TYPE_SYMBOLTABLE //zval保存的是全局符号表, 这个在我之前做了一个调整以后没用了, 但还保留着兼容,下个版本会去掉

    作用于字符串的有:

    1. #define IS_STR_PERSISTENT //是malloc分配内存的字符串
    2. #define IS_STR_INTERNED //INTERNED STRING
    3. #define IS_STR_PERMANENT //不可变的字符串, 用作哨兵作用
    4. #define IS_STR_CONSTANT //代表常量的字符串
    5. #define IS_STR_CONSTANT_UNQUALIFIED //带有可能命名空间的常量字符串

    作用于数组的有:

    1. #define IS_ARRAY_IMMUTABLE //同IS_TYPE_IMMUTABLE

    下面演示如何给 zval 赋予不同类型的值:

    1. zval name, numner, price, nothing, is_alive, imagick;
    2.  
    3. ZVAL_STRING(&name, "Sonny");
    4. ZVAL_LONG(&number, 12000);
    5. ZVAL_DOUBLE(&price, 15.50);
    6. ZVAL_BOOL(&is_alive, false);
    7.  
    8. object_init_ex(&imagick, imagick_ce);

    通常情况下,我们不会直接去改变 zval 内部变量,而是使用 Zend API 提供的方法进行管理。

    让我看下如何获取 zval 变量的值:

    1. char *str = Z_STRVAL(name);
    2. long number = Z_LVAL(numner);
    3. int bool_value = Z_BVA(is_alive);

    如果您想知道 zval 值的类型:

    1. int type = Z_TYPE(some_variable);
    2. if (type == IS_STRING) {
    3. // Is string!
    4. }

    哈希表是 PHP 内部非常重要的数据结构,除了最常见的数组,内核也随处用到,比如 function、class 的索引、符号表等等都用到了哈希表。

    PHP的实现也是类似,只是指向冲突元素的指针并没有直接存在Bucket中,而是存在嵌入的zval中,zval的结构:

    1. struct _zval_struct {
    2. zend_value value; /* value */
    3. union {
    4. struct {
    5. ZEND_ENDIAN_LOHI_4(
    6. zend_uchar type, /* active type */
    7. zend_uchar type_flags,
    8. zend_uchar const_flags,
    9. zend_uchar reserved) /* call info for EX(This) */
    10. } v;
    11. uint32_t type_info;
    12. } u1;
    13. union {
    14. uint32_t next; /* hash collision chain */
    15. uint32_t cache_slot; /* literal cache slot */
    16. uint32_t lineno; /* line number (for ast nodes) */
    17. uint32_t num_args; /* arguments number for EX(This) */
    18. uint32_t fe_pos; /* foreach position */
    19. uint32_t fe_iter_idx; /* foreach iterator index */
    20. } u2;
    21. };

    HashTable 中有两个非常相近的值:nNumUsed、nNumOfElements,nNumOfElements表示哈希表已有元素数,那这个值不跟nNumUsed一样吗?为什么要定义两个呢?实际上它们有不同的含义,当将一个元素从哈希表删除时并不会将对应的Bucket移除,而是将Bucket存储的zval标示为IS_UNDEF,只有扩容时发现nNumOfElements与nNumUsed相差达到一定数量(这个数量是:ht->nNumUsed - ht->nNumOfElements > (ht->nNumOfElements >> 5))时才会将已删除的元素全部移除,重新构建哈希表。所以nNumUsed>=nNumOfElements。

    HashTable 中另外一个非常重要的值 arData,这个值指向存储元素数组的第一个Bucket,插入元素时按顺序依次插入数组,比如第一个元素在arData[0]、第二个在arData[1]…arData[nNumUsed]。PHP数组的有序性正是通过arData保证的。

    zval.u2.next 存的就是冲突元素在Bucket数组中的位置,所以查找过程类似:

    1. zend_ulong h = zend_string_hash_val(key);
    2. uint32_t idx = ht->arHash[h & ht->nTableMask];
    3. while (idx != INVALID_IDX) {
    4. Bucket *b = &ht->arData[idx];
    5. if (b->h == h && zend_string_equals(b->key, key)) {
    6. return b;
    7. }
    8. idx = Z_NEXT(b->val); // b->val.u2.next
    9. }
    10. return NULL;

    该结构体帮助我们定义类,包括它的名字、方法、属性等等。

    1. //Get the class entry
    2. class_entry = Z_OBJCE_P(this_ptr);
    3.  
    4. //Print the class name
    5. fprintf(stdout, "%s", class_entry->name);