深入理解PHP中的empty和isset函數_php執行個體

來源:互聯網
上載者:User

近日被問到PHP中empty和isset函數時怎麼判斷變數的,剛開始我是一臉懵逼的,因為我自己也只是一知半解,為了弄懂其真正的原理,趕緊翻開源碼研究研究。經過分析可發現兩個函數調用的都是同一個函數,因此本文將對兩個函數一起分析。

我在github有對PHP源碼更詳細的註解。感興趣的可以圍觀一下,給個star。PHP5.4源碼註解。可以通過commit記錄查看已添加的註解。

函數使用格式

empty

bool empty ( mixed $var )

判斷變數是否為空白。

isset

bool isset ( mixed $var [ , mixed $... ] )

判斷變數是否被設定且不為NULL。

參數說明

對於empty,在PHP5.5版本以前,empty只支援變數參數,其他類型的參數會導致解析錯誤,比如函數調用的結果不能作為參數。

對於isset,如果變數被如unset的函數設為NULL,則函數會返回false。如果多個參數被傳遞到isset函數,那麼只有所有參數都被設定isset函數才會返回true。從左至右計算,一旦遇到沒被設定的變數就停止。

運行樣本

$result = empty(0); // true$result = empty(null); // true$result = empty(false); // true$result = empty(array()); // true$result = empty('0'); // true$result = empty(1); // false$result = empty(callback function); // 報錯$a = null;$result = isset($a); // false;$a = 1;$result = isset($a); // true;$a = 1;$b = 2;$c = 3;$result = isset($a, $b, $c); // true$a = 1;$b = null;$c = 3;$result = isset($a, $b, $c); // false

找到函數的定義位置

實際上,empty不是一個函數,而是一個語言結構。語言結構是在PHP程式運行前編譯好的,因此不能像之前那樣簡單地搜尋"PHP_FUNCTION empty"或"ZEND_FUNCTION empty"查看其源碼。要想看empty等語言結構的源碼,先要理解PHP代碼執行的機制。

PHP執行代碼會經過4個步驟,其流程圖如下所示:


在第一個階段,即Scanning階段,程式會掃描zend_language_scanner.l檔案將代碼檔案轉換成語言片段。對於isset和empty函數來說,在zend_language_scanner.l檔案中搜尋empty和isset可以得到函數在此檔案中的宏定義如下:

<ST_IN_SCRIPTING>"isset" {return T_ISSET;}<ST_IN_SCRIPTING>"empty" {return T_EMPTY;}

接下來就到了Parsing階段,這個階段,程式將T_ISSET和T_EMPTY等Tokens轉換成有意義的運算式,此時會做文法分析,Tokens的yacc儲存在zend_language_parser.y檔案中,可以找到T_ISSET和T_EMPTY的定義

internal_functions_in_yacc:T_ISSET '(' isset_variables ')' { $$ = $3; }| T_EMPTY '(' variable ')' { zend_do_isset_or_isempty(ZEND_ISEMPTY, &$$, &$3 TSRMLS_CC); }| T_INCLUDE expr { zend_do_include_or_eval(ZEND_INCLUDE, &$$, &$2 TSRMLS_CC); }| T_INCLUDE_ONCE expr { zend_do_include_or_eval(ZEND_INCLUDE_ONCE, &$$, &$2 TSRMLS_CC); }| T_EVAL '(' expr ')' { zend_do_include_or_eval(ZEND_EVAL, &$$, &$3 TSRMLS_CC); }| T_REQUIRE expr { zend_do_include_or_eval(ZEND_REQUIRE, &$$, &$2 TSRMLS_CC); }| T_REQUIRE_ONCE expr { zend_do_include_or_eval(ZEND_REQUIRE_ONCE, &$$, &$2 TSRMLS_CC); };

isset和empty函數最終都執行了zend_do_isset_or_isempty函數,繼續尋找

grep -rn "zend_do_isset_or_isempty"

可以發現,此函數在zend_compile.c檔案中定義。

函數執行步驟

1、解析參數

2、檢查是否為可寫變數

3、如果是變數的op_type是IS_CV(編譯時間期的變數),則設定其opcode為ZEND_ISSET_ISEMPTY_VAR;否則從active_op_array中擷取下一個op值,根據其op值設定last_op的opcode。

4、設定了opcode之後,之後會交給zend_excute執行。

源碼解讀

IS_CV是編譯器使用的一種cache機制,這種變數儲存著它被引用的變數的地址,當一個變數第一次被引用的時候,就會被CV起來,以後這個變數的引用就不需要再去尋找active符號表了。

對於empty函數,到了opcode的步驟後,參閱opcode處理函數,可以知道,isset和empty在excute的時候執行的是ZEND_ISSET_ISEMPTY_VAR等一系列函數,以ZEND_ISSET_ISEMPTY_VAR_SPEC_CV_VAR_HANDLER為例,找到這個函數的定義在zend_vm_execute.h。查看函數可以知道,empty函數的最終執行函數是i_zend_is_true(),而i_zend_is_true函數定義在zend_execute.h。i_zend_is_true函數的核心代碼如下:

switch (Z_TYPE_P(op)) {    case IS_NULL:      result = 0;      break;    case IS_LONG:    case IS_BOOL:    case IS_RESOURCE:      // empty參數為整數時非0的話就為false      result = (Z_LVAL_P(op)?1:0);      break;    case IS_DOUBLE:      result = (Z_DVAL_P(op) ? 1 : 0);      break;    case IS_STRING:      if (Z_STRLEN_P(op) == 0        || (Z_STRLEN_P(op)==1 && Z_STRVAL_P(op)[0]=='0')) {        // empty("0") == true        result = 0;      } else {        result = 1;      }      break;    case IS_ARRAY:      // empty(array) 是根據數組的數量來判斷      result = (zend_hash_num_elements(Z_ARRVAL_P(op))?1:0);      break;    case IS_OBJECT:      if(IS_ZEND_STD_OBJECT(*op)) {        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;  }

這段代碼比較直觀,函數沒有對檢測值做任何的轉換,通過這段代碼來進一步分析樣本中的empty函數做分析:
empty(null),到IS_NULL分支,result=0,i_zend_is_true() == 0,!i_zend_is_true() == 1,因此返回true。

empty(false),到IS_BOOL分支,result = ZLVAL_P(false) = 0,i_zend_is_true() == 0,!i_zend_is_true() == 1,因此返回true。

empty(array()),到IS_ARRAY分支,result = zend_hash_num_elements(Z_ARRVAL_P(op)) ? 1 : 0),zend_hash_num_elements返回數組元素的數量,array為空白,因此result為0,i_zend_is_true() == 0,!i_zend_is_true() == 1,因此返回true。

empty('0'),到IS_STRING分支,因為Z_STRLENP(op) == 1 且 Z_STRVAL_P(op)[0] == '0',因此result為0,i_zend_is_true() == 0,!i_zend_is_true() == 1,因此返回true。

empty(1),到IS_LONG分支,result = Z_LVAL_P(op) = 1,i_zend_is_true == 1,!i_zend_is_true() == 0,因此返回false。

 對於isset函數,最終實現判斷的代碼是:

if (isset && Z_TYPE_PP(value) != IS_NULL) {  ZVAL_BOOL(&EX_T(opline->result.var).tmp_var, 1);} else {  ZVAL_BOOL(&EX_T(opline->result.var).tmp_var, 0);}

只要value被設定了且不為NULL,isset函數就返回true。

小結

這次閱讀這兩個函數的源碼,學習到了:

1、PHP代碼在編譯期間的執行步驟

2、如何尋找PHP語言結構的源碼位置

3、如何尋找opcode處理函數的具體函數

學無止境,每個人都有自己的短板,只有通過不斷學習才能將自己的短板補上。

原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。

如果本文對你有協助,請點下推薦吧,謝謝^_^

以上這篇深入理解PHP中的empty和isset函數就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援雲棲社區。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.