常见的结构(Common Structures)¶
框架中最常用的变量是 zval。一个PHP应用程序中的每个值是一个变量。zval 是一个多态性结构,即一个 zval 变量可以是任何值(bool、string、long、double、array等)。浏览 Phalcon7 内部的一些代码,你会看到,大多数变量的声明 zval。PHP具有标量数据类型和非标量数据类型(array 和 object)。
PHP7中的zval的类型做了比较大的调整, 总体来说有如下17种类型:
作用于zval的有:
- #define IS_TYPE_CONSTANT //是常量类型
- #define IS_TYPE_IMMUTABLE //不可变的类型, 比如存在共享内存的数组
- #define IS_TYPE_REFCOUNTED //需要引用计数的类型
- #define IS_TYPE_COLLECTABLE //可能包含循环引用的类型(IS_ARRAY, IS_OBJECT)
- #define IS_TYPE_COPYABLE //可被复制的类型, 还记得我之前讲的对象和资源的例外么? 对象和资源就不是
- #define IS_TYPE_SYMBOLTABLE //zval保存的是全局符号表, 这个在我之前做了一个调整以后没用了, 但还保留着兼容,下个版本会去掉
作用于字符串的有:
- #define IS_STR_PERSISTENT //是malloc分配内存的字符串
- #define IS_STR_INTERNED //INTERNED STRING
- #define IS_STR_PERMANENT //不可变的字符串, 用作哨兵作用
- #define IS_STR_CONSTANT //代表常量的字符串
- #define IS_STR_CONSTANT_UNQUALIFIED //带有可能命名空间的常量字符串
作用于数组的有:
- #define IS_ARRAY_IMMUTABLE //同IS_TYPE_IMMUTABLE
下面演示如何给 zval 赋予不同类型的值:
- zval name, numner, price, nothing, is_alive, imagick;
- ZVAL_STRING(&name, "Sonny");
- ZVAL_LONG(&number, 12000);
- ZVAL_DOUBLE(&price, 15.50);
- ZVAL_BOOL(&is_alive, false);
- object_init_ex(&imagick, imagick_ce);
通常情况下,我们不会直接去改变 zval 内部变量,而是使用 Zend API 提供的方法进行管理。
让我看下如何获取 zval 变量的值:
- char *str = Z_STRVAL(name);
- long number = Z_LVAL(numner);
- int bool_value = Z_BVA(is_alive);
如果您想知道 zval 值的类型:
- int type = Z_TYPE(some_variable);
- if (type == IS_STRING) {
- // Is string!
- }
哈希表是 PHP 内部非常重要的数据结构,除了最常见的数组,内核也随处用到,比如 function、class 的索引、符号表等等都用到了哈希表。
PHP的实现也是类似,只是指向冲突元素的指针并没有直接存在Bucket中,而是存在嵌入的zval中,zval的结构:
- struct _zval_struct {
- zend_value value; /* value */
- union {
- struct {
- ZEND_ENDIAN_LOHI_4(
- zend_uchar type, /* active type */
- zend_uchar type_flags,
- zend_uchar const_flags,
- zend_uchar reserved) /* call info for EX(This) */
- } v;
- uint32_t type_info;
- } u1;
- union {
- uint32_t next; /* hash collision chain */
- uint32_t cache_slot; /* literal cache slot */
- uint32_t lineno; /* line number (for ast nodes) */
- uint32_t num_args; /* arguments number for EX(This) */
- uint32_t fe_pos; /* foreach position */
- uint32_t fe_iter_idx; /* foreach iterator index */
- } u2;
- };
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数组中的位置,所以查找过程类似:
- zend_ulong h = zend_string_hash_val(key);
- uint32_t idx = ht->arHash[h & ht->nTableMask];
- while (idx != INVALID_IDX) {
- Bucket *b = &ht->arData[idx];
- if (b->h == h && zend_string_equals(b->key, key)) {
- return b;
- }
- idx = Z_NEXT(b->val); // b->val.u2.next
- }
- return NULL;
该结构体帮助我们定义类,包括它的名字、方法、属性等等。
- //Get the class entry
- class_entry = Z_OBJCE_P(this_ptr);
- //Print the class name
- fprintf(stdout, "%s", class_entry->name);