原:PHP內核源碼分析:isset與 empty

聲明:本文爲斯人原創,全部爲作者一一分析得之,有不對的地方望賜教。
博客地址:PHP技術博客 在CSDN也會同步更新的哦.
歡迎轉載,轉載請註明出處 

PHP內核函數裏面有提供了兩個函數用來檢測 變量
isset和empty
這兩個有什麼區別?
我們用PHP代碼來檢測一下

[php]
<?php
//第一種
var_dump(empty($a)); //true 爲空
var_dump(isset($a)); //false 未設置
//第二種
$b=0;
var_dump(empty($b)); //true 爲空
var_dump(isset($b)); //true 已設置
//第三種
$c=0;
unset($c);
var_dump(empty($c)); //true 爲空
var_dump(isset($c)); //false 未設置
//第四種
$d=NULL;
var_dump(empty($c)); //true 爲空
var_dump(isset($c)); //false 未設置
?>
[/php]


經過試驗發現,empty不僅檢測是否設置 而且還檢測 是否爲0,如果爲0 也返回空
而 isset只要 變量設置,並不等於NULL或者沒有unset 就返回true
下面看看PHP的內核源碼詳細分析下

我們先來看看 分別執行 isset和empty ,PHP生成的op
isset:

[c]
2 0 > ZEND_ISSET_ISEMPTY_VAR 5 RES[ IS_TMP_VAR ~0 ] OP1[ IS_CV !0 ] OP2[ IS_UNUSED ]
1 FREE OP1[ IS_TMP_VAR ~0 ]
4 2 > RETURN OP1[ IS_CONST (0) 1 ]
[/c]

empty :

[c]
2 0 > ZEND_ISSET_ISEMPTY_VAR 6 RES[ IS_TMP_VAR ~0 ] OP1[ IS_CV !0 ] OP2[ IS_UNUSED ]
1 FREE OP1[ IS_TMP_VAR ~0 ]
4 2 > RETURN OP1[ IS_CONST (0) 1 ]
[/c]

經過觀察發現,他們是一樣的..op指向的handler都是ZEND_ISSET_ISEMPTY_VAR,說明他們執行了同樣的檢測函數
兩個YACC所對應的代碼是
T_EMPTY '(' variable ')' { zend_do_isset_or_isempty(ZEND_ISEMPTY, &$$, &$3 TSR MLS_CC);
還有
variable { zend_do_isset_or_isempty(ZEND_ISSET, &$$, &$1 TSRMLS_CC);

驗證了我們上面的推斷,都執行了共同的函數 zend_do_isset_or_isempty,只是第一個傳參不同
看看zend_do_isset_or_isempty的定義

[c]
void zend_do_isset_or_isempty(int type, znode *result, znode *variable TSRMLS_DC) /* {{{ */
{
zend_op *last_op;

zend_do_end_variable_parse(variable, BP_VAR_IS, 0 TSRMLS_CC);
//此函數下面有特別說明
zend_check_writable_variable(variable);

if (variable->op_type == IS_CV) { //如果已經編譯過 也就是參數是一個變量
last_op = get_next_op(CG(active_op_array) TSRMLS_CC); //創建一個 zend_op
last_op->opcode = ZEND_ISSET_ISEMPTY_VAR;
last_op->op1 = *variable;
SET_UNUSED(last_op->op2);
last_op->op2.u.EA.type = ZEND_FETCH_LOCAL; //當前作用域
last_op->result.u.var = get_temporary_variable(CG(active_op_array));
last_op->extended_value = ZEND_QUICK_SET;
} else {
last_op = &CG(active_op_array)->opcodes[get_next_op_number(CG(active_op_array))-1];

switch (last_op->opcode) {
case ZEND_FETCH_IS:
last_op->opcode = ZEND_ISSET_ISEMPTY_VAR;
break;
case ZEND_FETCH_DIM_IS:
last_op->opcode = ZEND_ISSET_ISEMPTY_DIM_OBJ;
break;
case ZEND_FETCH_OBJ_IS:
last_op->opcode = ZEND_ISSET_ISEMPTY_PROP_OBJ;
break;
}
last_op->extended_value = 0;
}
//result爲臨時變量
last_op->result.op_type = IS_TMP_VAR;
last_op->extended_value |= type;
*result = last_op->result;
}
[/c]


zend_check_writable_variable這裏要特別說明一下
isset和empty只檢測變量,如果傳遞一個方法或函數進來,會報錯,這個函數就是來檢測傳遞進來的參數是否是一個函數或方法
如果是會拋出錯誤.
下面是定義

[c]
void zend_check_writable_variable(const znode *variable) /* {{{ */
{
zend_uint type = variable->u.EA.type;

if (type & ZEND_PARSED_METHOD_CALL) {//類型是方法?
zend_error(E_COMPILE_ERROR, "Can't use method return value in write context");
}
if (type == ZEND_PARSED_FUNCTION_CALL) {//類型是函數?
zend_error(E_COMPILE_ERROR, "Can't use function return value in write context");
}
}
[/c]

zend_do_isset_or_isempty的作用就是 創建一個ZEND_OP..那麼好像並沒有 empty或者isset的作用啊..
這個時候就不得不提一下 op的執行過程了
創建定義好 _zend_op 之後..
解釋器引擎會最終將op在zend_execute函數裏執行,
zend_execute只是一個指針函數 它指向 zend_vm_executes.h裏面的execute函數
詳情請查看 >>>
execute 首先會構造zend_execute_data 指針,設置參數把op中間代碼全都放在execute_data裏.
execute_data下面會講到
last_op->opcode = ZEND_ISSET_ISEMPTY_VAR;
它會被execute解析 成
ZEND_ISSET_ISEMPTY_VAR_SPEC_CV_HANDLER 函數

[c]
static int ZEND_FASTCALL ZEND_ISSET_ISEMPTY_VAR_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS){
zend_op *opline = EX(opline); //當前執行的OP
zval **value;
zend_bool isset = 1;
if (IS_CV == IS_CV && (opline->extended_value & ZEND_QUICK_SET)) { //參數是解析的變量
if (EX(CVs)[opline->op1.u.var]) { //在上面zend_do_isset_or_isempty已經處理過,索引號保存在opline->op1.u.var裏面
value = EX(CVs)[opline->op1.u.var];//獲取到這個值
} else if (EG(active_symbol_table)) { //如果zend_do_isset_or_isempty沒有處理就要從局部變量表裏面去找
zend_compiled_variable *cv = &CV_DEF_OF(opline->op1.u.var);
if (zend_hash_quick_find(EG(active_symbol_table), cv->name, cv->name_len+1, cv->hash_value, (void **) &value) == FAILURE) {
isset = 0; //沒有找到 也就是沒有設置 ,那麼isset=0
}
} else {
isset = 0;
}
} else {
HashTable *target_symbol_table;

zval tmp, *varname = _get_zval_ptr_cv(&opline->op1, EX(Ts), BP_VAR_IS TSRMLS_CC);

if (Z_TYPE_P(varname) != IS_STRING) {
tmp = *varname;
zval_copy_ctor(&tmp);
convert_to_string(&tmp);
varname = &tmp;
}

if (opline->op2.u.EA.type == ZEND_FETCH_STATIC_MEMBER) {
if (!value) {
isset = 0;
}
} else {
isset = 0;
}
}

if (varname == &tmp) {
zval_dtor(&tmp);
}

}

Z_TYPE(EX_T(opline->result.u.var).tmp_var) = IS_BOOL;
//這裏就是 isset和 empty的區別了
switch (opline->extended_value & ZEND_ISSET_ISEMPTY_MASK) {
case ZEND_ISSET:
if (isset && Z_TYPE_PP(value) == IS_NULL) {
Z_LVAL(EX_T(opline->result.u.var).tmp_var) = 0;
} else {
Z_LVAL(EX_T(opline->result.u.var).tmp_var) = isset;
}
break;
case ZEND_ISEMPTY:
if (!isset || !i_zend_is_true(*value)) {
Z_LVAL(EX_T(opline->result.u.var).tmp_var) = 1;
} else {
Z_LVAL(EX_T(opline->result.u.var).tmp_var) = 0;
}
break;
}

ZEND_VM_NEXT_OPCODE();
}
[/c]


ZEND_OPCODE_HANDLER_ARGS 展開爲
#define ZEND_OPCODE_HANDLER_ARGS zend_execute_data *execute_data TSRMLS_DC
zend_execute_data 用來保存在PHP執行是的數據 ,比如 當前執行的代碼 ,局部變量以及在上一條中間代碼執行的數據等,

[c]
struct _zend_execute_data {
struct _zend_op *opline; //當前執行的op
zend_function_state function_state; //當前執行的函數表 包括用戶定義的所有信息,參數,名稱,類作用域等
zend_function *fbc; /* Function Being Called */ //以及執行過的函數
zend_class_entry *called_scope; //函數的作用域
zend_op_array *op_array;
zval *object;
union _temp_variable *Ts;
zval ***CVs;
HashTable *symbol_table; //局部變量符號表
struct _zend_execute_data *prev_execute_data; //上一個op的數據
zval *old_error_reporting;
zend_bool nested;
zval **original_return_value;
zend_class_entry *current_scope;
zend_class_entry *current_called_scope;
zval *current_this;
zval *current_object;
struct _zend_op *call_opline;
};
[/c]


裏面的屬性用 EX宏來獲取

源碼註釋已經基本清楚了 重點來看一下

[c]
//這裏就是 isset和 empty的區別了
switch (opline->extended_value & ZEND_ISSET_ISEMPTY_MASK) {
case ZEND_ISSET:
if (isset && Z_TYPE_PP(value) == IS_NULL) {
Z_LVAL(EX_T(opline->result.u.var).tmp_var) = 0;
} else {
Z_LVAL(EX_T(opline->result.u.var).tmp_var) = isset;
}
break;
case ZEND_ISEMPTY:
if (!isset || !i_zend_is_true(*value)) {
Z_LVAL(EX_T(opline->result.u.var).tmp_var) = 1;
} else {
Z_LVAL(EX_T(opline->result.u.var).tmp_var) = 0;
}
break;
}
[/c]


這段代碼之前的判斷都是一樣的區別如下:
isset:如果 變量設置了,但是類型確是NULL,那麼就直接返回false

empty: 返回的狀態與 i_zend_is_true返回的狀態相同
看看i_zend_is_true

[c]
static inline int i_zend_is_true(zval *op)
{
int result;

switch (Z_TYPE_P(op)) {
case IS_NULL:
result = 0; //如果爲NULL 返回0
break;
case IS_LONG:
case IS_BOOL:
case IS_RESOURCE:
result = (Z_LVAL_P(op)?1:0); //LONG,BOOL,RESOURCE的值不爲空,返回真
break;
case IS_DOUBLE:
result = (Z_DVAL_P(op) ? 1 : 0); //浮點數的值爲真 返回真
break;
case IS_STRING://字符型:長度爲0或者長度等於一且首地址等於0 返回假
if (Z_STRLEN_P(op) == 0
|| (Z_STRLEN_P(op)==1 && Z_STRVAL_P(op)[0]=='0')) {
result = 0;
} else {
result = 1;
}
break;
case IS_ARRAY://數組:如果數組的個數大於0則爲真
result = (zend_hash_num_elements(Z_ARRVAL_P(op))?1:0);
break;
case IS_OBJECT://對象
if(IS_ZEND_STD_OBJECT(*op)) { //是OBJECT
TSRMLS_FETCH();

if (Z_OBJ_HT_P(op)->cast_object) {
zval tmp;
if (Z_OBJ_HT_P(op)->cast_object(op, &tmp, IS_BOOL TSRMLS_CC) == SUCCESS) {
result = Z_LVAL(tmp);
break;
}
} else if (Z_OBJ_HT_P(op)->get) {
zval *tmp = Z_OBJ_HT_P(op)->get(op TSRMLS_CC);
if(Z_TYPE_P(tmp) != IS_OBJECT) {
/* for safety - avoid loop */
convert_to_boolean(tmp);
result = Z_LVAL_P(tmp);
zval_ptr_dtor(&tmp);
break;
}
}
}
result = 1;
break;
default:
result = 0;
break;
}
return result;
}
[/c]

經過分析
isset 只判斷 變量是否設置 並且值是否爲NULL
empty 則根據不同的數據類型做了不同的處理


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章