PHP弱類型語法的實現

PHP弱類型語法的實現

前言

借鑑了 TIPI, 對 php 源碼進行學習
歡迎你們給予意見, 互相溝通學習

弱類型語法實現方式 (弱變量容器 zval)

全部變量用同一結構表示, 既表示變量值, 也表示變量類型php

  • 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 結構數據結構

  • 對象存儲在一個對象容器中app

  • 每一個對象自帶操做函數函數

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;

弱類型語言的實現舉例 (類型轉換)

  • 隱式轉換ui

  • 在不一樣的操做函數自定義, 以字符串鏈接爲例debug

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 實現弱類型的核心在於: 全部變量使用同一種數據結構保存, 這個結構不只表示變量的值, 也表示變量類型

相關文章
相關標籤/搜索