常见的结构(Common Structures)

首先让我们来了解一些底层的常用的结构。

变量(Zvals)

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

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

  1. /* regular data types */
  2. #define IS_UNDEF 0
  3. #define IS_NULL 1
  4. #define IS_FALSE 2
  5. #define IS_TRUE 3
  6. #define IS_LONG 4
  7. #define IS_DOUBLE 5
  8. #define IS_STRING 6
  9. #define IS_ARRAY 7
  10. #define IS_OBJECT 8
  11. #define IS_RESOURCE 9
  12. #define IS_REFERENCE 10
  13.  
  14. /* constant expressions */
  15. #define IS_CONSTANT 11
  16. #define IS_CONSTANT_AST 12
  17.  
  18. /* fake types */
  19. #define _IS_BOOL 13
  20. #define IS_CALLABLE 14
  21.  
  22. /* internal types */
  23. #define IS_INDIRECT 15 //间接变量,跟 compiled variables(CV) table 有关系
  24. #define IS_PTR 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

作用于对象的有:

  1. #define IS_OBJ_APPLY_COUNT //递归保护
  2. #define IS_OBJ_DESTRUCTOR_CALLED //析构函数已经调用
  3. #define IS_OBJ_FREE_CALLED //清理函数已经调用
  4. #define IS_OBJ_USE_GUARDS //魔术方法递归保护
  5. #define IS_OBJ_HAS_GUARDS //是否有魔术方法递归保护标志

下面演示如何给 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_NULL(&nothing);
  7. ZVAL_BOOL(&is_alive, false);
  8.  
  9. 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. }

哈希表(HashTable)

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

  1. typedef struct _Bucket {
  2. zval val;
  3. zend_ulong h; /* hash value (or numeric index) */
  4. zend_string *key; /* string key or NULL for numerics */
  5. } Bucket;
  6.  
  7. typedef struct _zend_array HashTable;
  8.  
  9. struct _zend_array {
  10. zend_refcounted_h gc;
  11. union {
  12. struct {
  13. ZEND_ENDIAN_LOHI_4(
  14. zend_uchar flags,
  15. zend_uchar nApplyCount,
  16. zend_uchar nIteratorsCount,
  17. zend_uchar reserve)
  18. } v;
  19. uint32_t flags;
  20. } u;
  21. uint32_t nTableMask; // 哈希值计算掩码,等于nTableSize的负值(nTableMask = ~nTableSize + 1)
  22. Bucket *arData; // 存储元素数组,指向第一个Bucket
  23. uint32_t nNumUsed; // 已用Bucket数
  24. uint32_t nNumOfElements; // 哈希表已有元素数
  25. uint32_t nTableSize; // 哈希表总大小,为2的n次方
  26. uint32_t nInternalPointer;
  27. zend_long nNextFreeElement; // 下一个可用的数值索引,如:arr[] = 1;arr["a"] = 2;arr[] = 3; 则nNextFreeElement = 2;
  28. dtor_func_t pDestructor;
  29. };

哈希碰撞

哈希碰撞是指不同的key可能计算得到相同的哈希值(数值索引的哈希值直接就是数值本身),但是这些值又需要插入同一个哈希表。一般解决方法是将Bucket串成链表,查找时遍历链表比较key。

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 var_flags;
  15. uint32_t next; /* hash collision chain */
  16. uint32_t cache_slot; /* literal cache slot */
  17. uint32_t lineno; /* line number (for ast nodes) */
  18. uint32_t num_args; /* arguments number for EX(This) */
  19. uint32_t fe_pos; /* foreach position */
  20. uint32_t fe_iter_idx; /* foreach iterator index */
  21. } u2;
  22. };

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;

类的原型(Zend Class Entries)

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

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

类的标准操作集合(handlers)

  1. struct _zend_object {
  2. zend_refcounted_h gc;
  3. uint32_t handle; // TODO: may be removed ???
  4. zend_class_entry *ce;
  5. const zend_object_handlers *handlers;
  6. HashTable *properties;
  7. zval properties_table[1];
  8. };
  9.  
  10. /* Used to call methods */
  11. /* args on stack! */
  12. /* Andi - EX(fbc) (function being called) needs to be initialized already in the INIT fcall opcode so that the parameters can be parsed the right way. We need to add another callback for this.
  13. */
  14. typedef int (*zend_object_call_method_t)(zend_string *method, zend_object *object, INTERNAL_FUNCTION_PARAMETERS);
  15. typedef union _zend_function *(*zend_object_get_method_t)(zend_object **object, zend_string *method, const zval *key);
  16. typedef union _zend_function *(*zend_object_get_constructor_t)(zend_object *object);
  17.  
  18. /* Object maintenance/destruction */
  19. typedef void (*zend_object_dtor_obj_t)(zend_object *object);
  20. typedef void (*zend_object_free_obj_t)(zend_object *object);
  21. typedef zend_object* (*zend_object_clone_obj_t)(zval *object);
  22.  
  23. /* Get class name for display in var_dump and other debugging functions.
  24. * Must be defined and must return a non-NULL value. */
  25. typedef zend_string *(*zend_object_get_class_name_t)(const zend_object *object);
  26.  
  27. typedef int (*zend_object_compare_t)(zval *object1, zval *object2);
  28. typedef int (*zend_object_compare_zvals_t)(zval *resul, zval *op1, zval *op2);
  29.  
  30. /* Cast an object to some other type
  31. */
  32. typedef int (*zend_object_cast_t)(zval *readobj, zval *retval, int type);
  33.  
  34. /* updates *count to hold the number of elements present and returns SUCCESS.
  35. * Returns FAILURE if the object does not have any sense of overloaded dimensions */
  36. typedef int (*zend_object_count_elements_t)(zval *object, zend_long *count);
  37.  
  38. typedef int (*zend_object_get_closure_t)(zval *obj, zend_class_entry **ce_ptr, union _zend_function **fptr_ptr, zend_object **obj_ptr);
  39.  
  40. typedef HashTable *(*zend_object_get_gc_t)(zval *object, zval **table, int *n);
  41.  
  42. typedef int (*zend_object_do_operation_t)(zend_uchar opcode, zval *result, zval *op1, zval *op2);
  43.  
  44. struct _zend_object_handlers {
  45. /* offset of real object header (usually zero) */
  46. int offset;
  47. /* general object functions */
  48. zend_object_free_obj_t free_obj;
  49. zend_object_dtor_obj_t dtor_obj;
  50. zend_object_clone_obj_t clone_obj;
  51. /* individual object functions */
  52. zend_object_read_property_t read_property;
  53. zend_object_write_property_t write_property;
  54. zend_object_read_dimension_t read_dimension;
  55. zend_object_write_dimension_t write_dimension;
  56. zend_object_get_property_ptr_ptr_t get_property_ptr_ptr;
  57. zend_object_get_t get;
  58. zend_object_set_t set;
  59. zend_object_has_property_t has_property;
  60. zend_object_unset_property_t unset_property;
  61. zend_object_has_dimension_t has_dimension;
  62. zend_object_unset_dimension_t unset_dimension;
  63. zend_object_get_properties_t get_properties;
  64. zend_object_get_method_t get_method;
  65. zend_object_call_method_t call_method;
  66. zend_object_get_constructor_t get_constructor;
  67. zend_object_get_class_name_t get_class_name;
  68. zend_object_compare_t compare_objects;
  69. zend_object_cast_t cast_object;
  70. zend_object_count_elements_t count_elements;
  71. zend_object_get_debug_info_t get_debug_info;
  72. zend_object_get_closure_t get_closure;
  73. zend_object_get_gc_t get_gc;
  74. zend_object_do_operation_t do_operation;
  75. zend_object_compare_zvals_t compare;
  76. };