在PHP中,使用者函數的定義從function關鍵字開始。如下所示簡單示例:
function foo($var) {
echo $var;
}
這是一個非常簡單的函數,它所實作的功能是定義一個函數,函數有一個參數,函數的内容是在标準輸出端輸出傳遞給它的參數變量的值。
函數的一切從function開始。我們從function開始函數定義的探索之旅。
詞法分析
在 Zend/zend_language_scanner.l中我們找到如下所示的代碼:
"function" {
return T_FUNCTION;
}
它所表示的含義是function将會生成T_FUNCTION标記。在擷取這個标記後,我們開始文法分析。
文法分析
在 Zend/zend_language_parser.y檔案中找到函數的聲明過程标記如下:
function:
T_FUNCTION { $$.u.opline_num = CG(zend_lineno); }
;
is_reference:
/* empty */ { $$.op_type = ZEND_RETURN_VAL; }
| '&' { $$.op_type = ZEND_RETURN_REF; }
;
unticked_function_declaration_statement:
function is_reference T_STRING {
zend_do_begin_function_declaration(&$1, &$3, 0, $2.op_type, NULL TSRMLS_CC); }
'(' parameter_list ')' '{' inner_statement_list '}' {
zend_do_end_function_declaration(&$1 TSRMLS_CC); }
;
關注點在 function is_reference T_STRING,表示function關鍵字,是否引用,函數名。
T_FUNCTION标記隻是用來定位函數的聲明,表示這是一個函數,而更多的工作是與這個函數相關的東西,包括參數,傳回值等。
生成中間代碼
文法解析後,我們看到所執行編譯函數為zend_do_begin_function_declaration。在 Zend/zend_complie.c檔案中找到其實作如下:
void zend_do_begin_function_declaration(znode *function_token, znode *function_name,
int is_method, int return_reference, znode *fn_flags_znode TSRMLS_DC) /* {{{ */
{
...//省略
function_token->u.op_array = CG(active_op_array);
lcname = zend_str_tolower_dup(name, name_len);
orig_interactive = CG(interactive);
CG(interactive) = 0;
init_op_array(&op_array, ZEND_USER_FUNCTION, INITIAL_OP_ARRAY_SIZE TSRMLS_CC);
CG(interactive) = orig_interactive;
...//省略
if (is_method) {
...//省略 類方法 在後面的類章節介紹
} else {
zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);
opline->opcode = ZEND_DECLARE_FUNCTION;
opline->op1.op_type = IS_CONST;
build_runtime_defined_function_key(&opline->op1.u.constant, lcname,
name_len TSRMLS_CC);
opline->op2.op_type = IS_CONST;
opline->op2.u.constant.type = IS_STRING;
opline->op2.u.constant.value.str.val = lcname;
opline->op2.u.constant.value.str.len = name_len;
Z_SET_REFCOUNT(opline->op2.u.constant, 1);
opline->extended_value = ZEND_DECLARE_FUNCTION;
zend_hash_update(CG(function_table), opline->op1.u.constant.value.str.val,
opline->op1.u.constant.value.str.len, &op_array, sizeof(zend_op_array),
(void **) &CG(active_op_array));
}
}
/* }}} */
生成的中間代碼為 ZEND_DECLARE_FUNCTION ,根據這個中間代碼及操作數對應的op_type。 我們可以找到中間代碼的執行函數為 ZEND_DECLARE_FUNCTION_SPEC_HANDLER。
在生成中間代碼時,可以看到已經統一了函數名全部為小寫,表示函數的名稱不是區分大小寫的。
為驗證這個實作,我們看一段代碼:
function T() {
echo 1;
}
function t() {
echo 2;
}
執行代碼,可以看到螢幕上輸出如下報錯資訊:
Fatal error: Cannot redeclare t() (previously declared in ...)
表示對于PHP來說T和t是同一個函數名。檢驗函數名是否重複,這個過程是在哪進行的呢? 下面将要介紹的函數聲明中間代碼的執行過程包含了這個檢查過程。
執行中間代碼
在 Zend/zend_vm_execute.h 檔案中找到 ZEND_DECLARE_FUNCTION中間代碼對應的執行函數:ZEND_DECLARE_FUNCTION_SPEC_HANDLER。 此函數隻調用了函數do_bind_function。其調用代碼為: