PHP弱类型语法的实现
前言
借鉴了 TIPI, 对 php 源码进行学习
欢迎大家给予意见, 互相沟通学习
弱类型语法实现方式 (弱变量容器 zval)
所有变量用同一结构表示, 既表示变量值, 也表示变量类型
- zval 结构
struct _zval_struct { // PHP存储变量的结构
zvalue_value value; // 值
zend_uint refcount__gc; // 引用计数
zend_uchar type; // 类型标识, IS_NULL, IS_BOOL, IS_STRING ...
zend_uchar is_ref__gc; //是否为引用
};
// 变量的数据类型
#define IS_NULL 0 // null
#define IS_LONG 1 // long
#define IS_DOUBLE 2 // float, double
#define IS_BOOL 3 // boolean
#define IS_ARRAY 4 // array
#define IS_OBJECT 5 // object
#define IS_STRING 6 // string
#define IS_RESOURCE 7 // resource
#define IS_CONSTANT 8 // constant
#define IS_CONSTANT_ARRAY 9
#define IS_CALLABLE 10
不同类型的变量, 存储的属性字段不同
- value 结构
typedef union _zvalue_value { // 变量存储的值结构
long lval; // 存储整形或布尔型的值
double dval; // 存储浮点型的值
struct { // 存储字符串
char *val; // 字符串指针
int len; // 字符串长度
} str;
HashTable *ht; // 存储数组,参见zend_hash.h
zend_object_value obj; // 存储对象,参见zend_types.h
} zvalue_value;
对象类型相对特殊, zval 结构只是定义了对象的操作函数和对象所在位置 (在对象池中的位置)
object 结构
对象存储在一个对象容器中
每个对象自带操作函数
typedef unsigned int zend_object_handle;
// 对象的存储结构
typedef struct _zend_object_value {
/*
* php 内核会将所有对象存放在一个对象容器中: EG(objects_store).object_buckets
* handle 参数是对象在这个容器中的索引, 无符号整数
*/
zend_object_handle handle;
// 对象属性, 方法的操作函数: Zend/zend_object_handlers.h
const zend_object_handlers *handlers;
} zend_object_value;
- 对象池的概念
// 对象池, 存放 php 中间代码运行过程中生成的所有对象
typedef struct _zend_objects_store {
// 对象容器
zend_object_store_bucket *object_buckets;
zend_uint top;
zend_uint size;
int free_list_head;
} zend_objects_store;
/*
* 对象哈希表桶结构
* 解决冲突的哈希算法为链接法
* 哈希冲突解决办法有两种:
* 1. 链接法
* 2. 开放寻址法
*/
typedef struct _zend_object_store_bucket {
zend_bool destructor_called;
zend_bool valid;
zend_uchar apply_count;
union _store_bucket {
struct _store_object {
// 对象数据, zend_object 结构
void *object;
zend_objects_store_dtor_t dtor;
zend_objects_free_object_storage_t free_storage;
zend_objects_store_clone_t clone;
const zend_object_handlers *handlers;
zend_uint refcount;
gc_root_buffer *buffered;
} obj;
struct {
int next;
} free_list;
} bucket;
} zend_object_store_bucket;
- 函数方法定义, 不同的扩展创建的对象, 实现的方法不同 (以 php 标准库为例)
// 可以理解为对象方法的父类
struct _zend_object_handlers {
/* general object functions */
zend_object_add_ref_t add_ref;
zend_object_del_ref_t del_ref;
zend_object_clone_obj_t clone_obj;
/* individual object functions */
zend_object_read_property_t read_property;
zend_object_write_property_t write_property;
zend_object_read_dimension_t read_dimension;
zend_object_write_dimension_t write_dimension;
zend_object_get_property_ptr_ptr_t get_property_ptr_ptr;
zend_object_get_t get;
zend_object_set_t set;
zend_object_has_property_t has_property;
zend_object_unset_property_t unset_property;
zend_object_has_dimension_t has_dimension;
zend_object_unset_dimension_t unset_dimension;
zend_object_get_properties_t get_properties;
zend_object_get_method_t get_method;
zend_object_call_method_t call_method;
zend_object_get_constructor_t get_constructor;
zend_object_get_class_entry_t get_class_entry;
zend_object_get_class_name_t get_class_name;
zend_object_compare_t compare_objects;
zend_object_cast_t cast_object;
zend_object_count_elements_t count_elements;
zend_object_get_debug_info_t get_debug_info;
zend_object_get_closure_t get_closure;
zend_object_get_gc_t get_gc;
};
// 以 php 标准库为例调用的方法如下:
ZEND_API zend_object_handlers std_object_handlers = {
zend_objects_store_add_ref, /* add_ref */
zend_objects_store_del_ref, /* del_ref */
zend_objects_clone_obj, /* clone_obj */
zend_std_read_property, /* read_property */
zend_std_write_property, /* write_property */
zend_std_read_dimension, /* read_dimension */
zend_std_write_dimension, /* write_dimension */
zend_std_get_property_ptr_ptr, /* get_property_ptr_ptr */
NULL, /* get */
NULL, /* set */
zend_std_has_property, /* has_property */
zend_std_unset_property, /* unset_property */
zend_std_has_dimension, /* has_dimension */
zend_std_unset_dimension, /* unset_dimension */
zend_std_get_properties, /* get_properties */
zend_std_get_method, /* get_method */
NULL, /* call_method */
zend_std_get_constructor, /* get_constructor */
zend_std_object_get_class, /* get_class_entry */
zend_std_object_get_class_name, /* get_class_name */
zend_std_compare_objects, /* compare_objects */
zend_std_cast_object_tostring, /* cast_object */
NULL, /* count_elements */
NULL, /* get_debug_info */
zend_std_get_closure, /* get_closure */
zend_std_get_gc, /* get_gc */
};
- 最终存放在桶中的对象是经过封装的, 包含类信息 (zend_class_entry) 的对象实例
// 最终存储在对象哈希表中的对象结构
typedef struct _zend_object {
// 对象的类信息
zend_class_entry *ce;
// 属性信息
HashTable *properties;
zval **properties_table;
HashTable *guards; /* protects from __get/__set ... recursion */
} zend_object;
- 对象在 new 的时候都做了哪些事情
ZEND_API zend_object_value zend_objects_new(zend_object **object, zend_class_entry *class_type TSRMLS_DC)
{
zend_object_value retval;
*object = emalloc(sizeof(zend_object));
(*object)->ce = class_type;
(*object)->properties = NULL;
(*object)->properties_table = NULL;
(*object)->guards = NULL;
retval.handle = zend_objects_store_put(*object, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) zend_objects_free_object_storage, NULL TSRMLS_CC);
retval.handlers = &std_object_handlers;
return retval;
}
常量结构相对特殊, 它除了 zval 结构外, 还包含了其他的一些属性
- 常量结构
#define CONST_CS (1<<0) /* Case Sensitive */
#define CONST_PERSISTENT (1<<1) /* Persistent */
#define CONST_CT_SUBST (1<<2) /* Allow compile-time substitution */
// 常量结构
typedef struct _zend_constant {
// zval 结构
zval value;
// 标志位
int flags;
// 常量名称
char *name;
// 常量长度
uint name_len;
// 常量模块号
int module_number;
} zend_constant;
弱类型语言的实现举例 (类型转换)
隐式转换
在不同的操作函数自定义, 以字符串连接为例
ZEND_API int concat_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ */
{
zval op1_copy, op2_copy;
int use_copy1 = 0, use_copy2 = 0;
if (Z_TYPE_P(op1) != IS_STRING) {
zend_make_printable_zval(op1, &op1_copy, &use_copy1);
}
if (Z_TYPE_P(op2) != IS_STRING) {
zend_make_printable_zval(op2, &op2_copy, &use_copy2);
}
// 省略
}
ZEND_API void zend_make_printable_zval(zval *expr, zval *expr_copy, int *use_copy) /* {{{ */
{
if (Z_TYPE_P(expr)==IS_STRING) {
*use_copy = 0;
return;
}
switch (Z_TYPE_P(expr)) {
case IS_NULL:
Z_STRLEN_P(expr_copy) = 0;
Z_STRVAL_P(expr_copy) = STR_EMPTY_ALLOC();
break;
case IS_BOOL:
if (Z_LVAL_P(expr)) {
Z_STRLEN_P(expr_copy) = 1;
Z_STRVAL_P(expr_copy) = estrndup("1", 1);
} else {
Z_STRLEN_P(expr_copy) = 0;
Z_STRVAL_P(expr_copy) = STR_EMPTY_ALLOC();
}
break;
case IS_RESOURCE:
Z_STRVAL_P(expr_copy) = (char *) emalloc(sizeof("Resource id #") - 1 + MAX_LENGTH_OF_LONG);
Z_STRLEN_P(expr_copy) = snprintf(Z_STRVAL_P(expr_copy), sizeof("Resource id #") - 1 + MAX_LENGTH_OF_LONG, "Resource id #%ld", Z_LVAL_P(expr));
break;
case IS_ARRAY:
zend_error(E_NOTICE, "Array to string conversion");
Z_STRLEN_P(expr_copy) = sizeof("Array") - 1;
Z_STRVAL_P(expr_copy) = estrndup("Array", Z_STRLEN_P(expr_copy));
break;
case IS_OBJECT:
{
TSRMLS_FETCH();
// 直接转换成字符串
if (zend_std_cast_object_tostring(expr, expr_copy, IS_STRING TSRMLS_CC) == SUCCESS) {
break;
}
// 是否定义了 cast_object 函数
if (Z_OBJ_HANDLER_P(expr, cast_object)) {
zval *val;
ALLOC_ZVAL(val);
INIT_PZVAL_COPY(val, expr);
zval_copy_ctor(val);
// 调用转换函数
if (Z_OBJ_HANDLER_P(expr, cast_object)(val, expr_copy, IS_STRING TSRMLS_CC) == SUCCESS) {
zval_ptr_dtor(&val);
break;
}
zval_ptr_dtor(&val);
}
// 是否定义了 get 函数
if (!Z_OBJ_HANDLER_P(expr, cast_object) && Z_OBJ_HANDLER_P(expr, get)) {
zval *z = Z_OBJ_HANDLER_P(expr, get)(expr TSRMLS_CC);
Z_ADDREF_P(z);
if (Z_TYPE_P(z) != IS_OBJECT) {
zend_make_printable_zval(z, expr_copy, use_copy);
if (*use_copy) {
zval_ptr_dtor(&z);
} else {
ZVAL_ZVAL(expr_copy, z, 0, 1);
*use_copy = 1;
}
return;
}
zval_ptr_dtor(&z);
}
zend_error(EG(exception) ? E_ERROR : E_RECOVERABLE_ERROR, "Object of class %s could not be converted to string", Z_OBJCE_P(expr)->name);
Z_STRLEN_P(expr_copy) = 0;
Z_STRVAL_P(expr_copy) = STR_EMPTY_ALLOC();
}
break;
case IS_DOUBLE:
*expr_copy = *expr;
zval_copy_ctor(expr_copy);
zend_locale_sprintf_double(expr_copy ZEND_FILE_LINE_CC);
break;
default:
*expr_copy = *expr;
zval_copy_ctor(expr_copy);
convert_to_string(expr_copy);
break;
}
Z_TYPE_P(expr_copy) = IS_STRING;
*use_copy = 1;
}
显式转换
一系列的 "convert_to_" 函数实现, 以 convert_to_boolean 为例
ZEND_API void convert_to_boolean(zval *op) /* {{{ */
{
int tmp;
switch (Z_TYPE_P(op)) {
case IS_BOOL:
break;
case IS_NULL:
Z_LVAL_P(op) = 0;
break;
case IS_RESOURCE: {
TSRMLS_FETCH();
zend_list_delete(Z_LVAL_P(op));
}
/* break missing intentionally */
case IS_LONG:
Z_LVAL_P(op) = (Z_LVAL_P(op) ? 1 : 0);
break;
case IS_DOUBLE:
Z_LVAL_P(op) = (Z_DVAL_P(op) ? 1 : 0);
break;
case IS_STRING:
{
char *strval = Z_STRVAL_P(op);
if (Z_STRLEN_P(op) == 0
|| (Z_STRLEN_P(op)==1 && Z_STRVAL_P(op)[0]=='0')) {
Z_LVAL_P(op) = 0;
} else {
Z_LVAL_P(op) = 1;
}
STR_FREE(strval);
}
break;
case IS_ARRAY:
tmp = (zend_hash_num_elements(Z_ARRVAL_P(op))?1:0);
zval_dtor(op);
Z_LVAL_P(op) = tmp;
break;
case IS_OBJECT:
{
zend_bool retval = 1;
TSRMLS_FETCH();
convert_object_to_type(op, IS_BOOL, convert_to_boolean);
if (Z_TYPE_P(op) == IS_BOOL) {
return;
}
zval_dtor(op);
ZVAL_BOOL(op, retval);
break;
}
default:
zval_dtor(op);
Z_LVAL_P(op) = 0;
break;
}
Z_TYPE_P(op) = IS_BOOL;
}
总结
php 内核底层所有的操作都是基于 zval 的结构, 可以将其称之为
弱变量容器
弱类型的实现都是对于 zval 内的属性判断来区分类型, 调用不同的逻辑
为了方便, php 内核对于这些操作定义了一批操作宏
#define Z_LVAL(zval) (zval).value.lval
#define Z_BVAL(zval) ((zend_bool)(zval).value.lval)
#define Z_DVAL(zval) (zval).value.dval
#define Z_STRVAL(zval) (zval).value.str.val
#define Z_STRLEN(zval) (zval).value.str.len
#define Z_ARRVAL(zval) (zval).value.ht
#define Z_OBJVAL(zval) (zval).value.obj
#define Z_OBJ_HANDLE(zval) Z_OBJVAL(zval).handle
#define Z_OBJ_HT(zval) Z_OBJVAL(zval).handlers
#define Z_OBJCE(zval) zend_get_class_entry(&(zval) TSRMLS_CC)
#define Z_OBJPROP(zval) Z_OBJ_HT((zval))->get_properties(&(zval) TSRMLS_CC)
#define Z_OBJ_HANDLER(zval, hf) Z_OBJ_HT((zval))->hf // 省略 #define Z_TYPE(zval) (zval).type
#define Z_TYPE_P(zval_p) Z_TYPE(*zval_p)
#define Z_TYPE_PP(zval_pp) Z_TYPE(**zval_pp)简而言之, php 实现弱类型的核心在于:
所有变量使用同一种数据结构保存, 这个结构不仅表示变量的值, 也表示变量类型