在PHP中,函數有自己的作用域,同時在其内部可以實作各種語句的執行,最後傳回最終結果值。 在PHP的源碼中可以發現,PHP核心将函數分為以下類型:
#define ZEND_INTERNAL_FUNCTION 1
#define ZEND_USER_FUNCTION 2
#define ZEND_OVERLOADED_FUNCTION 3
#define ZEND_EVAL_CODE 4
#define ZEND_OVERLOADED_FUNCTION_TEMPORARY 5
其中的ZEND_USER_FUNCTION是使用者函數,ZEND_INTERNAL_FUNCTION是内置的函數。也就是說PHP将内置的函數和使用者定義的函數分别儲存。
使用者函數(ZEND_USER_FUNCTION)
使用者自定義函數是非常常用的函數種類,如下面的代碼,定義了一個使用者自定義的函數:
<?php
function nowamagic( $name ){
$return = "Hi! " . $name;
echo $return;
return $return;
}
?>
這個示例中,對自定義函數傳入了一個參數,并将其與Hi! 一起輸出并做為傳回值傳回。 從這個例子可以看出函數的基本特點:運作時聲明、可以傳參數、有值傳回。 當然,有些函數隻是進行一些操作,并不一定顯式的有傳回值,在PHP的實作中,即使沒有顯式的傳回, PHP核心也會“幫你“傳回NULL。
ZE在執行過程中,會将運作時資訊存儲于_zend_execute_data中:
struct _zend_execute_data {
//...省略部分代碼
zend_function_state function_state;
zend_function *fbc; /* Function Being Called */
//...省略部分代碼
};
在程式初始化的過程中,function_state也會進行初始化,function_state由兩個部分組成:
typedef struct _zend_function_state {
zend_function *function;
void **arguments;
} zend_function_state;
**arguments是一個指向函數參數的指針,而函數體本身則存儲于*function中, *function是一個zend_function結構體, 它最終存儲了使用者自定義函數的一切資訊,它的具體結構是這樣的:
typedef union _zend_function {
zend_uchar type; /* 如使用者自定義則為 #define ZEND_USER_FUNCTION 2
MUST be the first element of this struct! */
struct {
zend_uchar type; /* never used */
char *function_name; //函數名稱
zend_class_entry *scope; //函數所在的類作用域
zend_uint fn_flags; // 作為方法時的通路類型等,如ZEND_ACC_STATIC等
union _zend_function *prototype; //函數原型
zend_uint num_args; //參數數目
zend_uint required_num_args; //需要的參數數目
zend_arg_info *arg_info; //參數資訊指針
zend_bool pass_rest_by_reference;
unsigned char return_reference; //傳回值
} common;
zend_op_array op_array; //函數中的操作
zend_internal_function internal_function;
} zend_function;
zend_function的結構中的op_array存儲了該函數中所有的操作,當函數被調用時,ZE就會将這個op_array中的opline一條條順次執行, 并将最後的傳回值傳回。 從VLD擴充中檢視的關于函數的資訊可以看出,函數的定義和執行是分開的,一個函數可以作為一個獨立的運作單元而存在。
内部函數(ZEND_INTERNAL_FUNCTION)
ZEND_INTERNAL_FUNCTION函數是由擴充或者Zend/PHP核心提供的,用“C/C++”編寫的,可以直接執行的函數。如下為内部函數的結構:
typedef struct _zend_internal_function {
/* Common elements */
zend_uchar type;
char * function_name;
zend_class_entry *scope;
zend_uint fn_flags;
union _zend_function *prototype;
zend_uint num_args;
zend_uint required_num_args;
zend_arg_info *arg_info;
zend_bool pass_rest_by_reference;
unsigned char return_reference;
/* END of common elements */
void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
struct _zend_module_entry *module;
} zend_internal_function;
最常見的操作是在子產品初始化時,ZE會周遊每個載入的擴充子產品,然後将子產品中function_entry中指明的每一個函數(module->functions), 建立一個zend_internal_function結構, 并将其type設定為ZEND_INTERNAL_FUNCTION,将這個結構填入全局的函數表(HashTable結構); 函數設定及注冊過程見 Zend/zend_API.c檔案中的zend_register_functions函數。這個函數除了處理函數,也處理類的方法,包括那些魔術方法。
内部函數的結構與使用者自定義的函數結構基本類似,有一些不同,
- 調用方法,handler字段. 如果是ZEND_INTERNAL_FUNCTION, 那麼ZE就調用zend_execute_internal,通過zend_internal_function.handler來執行這個函數。 而使用者自定義的函數需要生成中間代碼,然後通過中間代碼映射到相對就把方法調用。
- 内置函數在結構中多了一個module字段,表示屬于哪個子產品。不同的擴充其子產品不同。
- type字段,在使用者自定義的函數中,type字段幾科無用,而内置函數中的type字段作為幾種内部函數的區分。
變量函數
PHP 支援變量函數的概念。這意味着如果一個變量名後有圓括号,PHP 将尋找與變量的值同名的函數,并且将嘗試執行它。 除此之外,這個可以被用于實作回調函數,函數表等。 對比使用變量函數和内部函數的調用:
變量函數$func
$func = 'print_r';
$func('i am print_r function.');
通過VLD來檢視這段代碼編譯後的中間代碼:
function name: (null)
number of ops: 9
compiled vars: !0 = $func
line # * op fetch ext return operands
--------------------------------------------------------------------------------
-
2 0 > EXT_STMT
1 ASSIGN !0, 'print_r'
3 2 EXT_STMT
3 INIT_FCALL_BY_NAME !0
4 EXT_FCALL_BEGIN
5 SEND_VAL 'i+am+print_r+function.'
6 DO_FCALL_BY_NAME 1
7 EXT_FCALL_END
8 > RETURN 1
内部函數print_r
print_r('i am print_r function.');
通過VLD來檢視這段代碼編譯後的中間代碼:
function name: (null)
number of ops: 6
compiled vars: none
line # * op fetch ext return operands
--------------------------------------------------------------------------------
-
2 0 > EXT_STMT
1 EXT_FCALL_BEGIN
2 SEND_VAL 'i+am+print_r+function.'
3 DO_FCALL 1 'print_r'
4 EXT_FCALL_END
5 > RETURN 1
對比發現,二者在調用的中間代碼上存在一些差別。變量函數是DO_FCALL_BY_NAME,而内部函數是DO_FCALL。 這在文法解析時就已經決定了, 見Zend/zend_complie.c檔案的zend_do_end_function_call函數中部分代碼:
if (!is_method && !is_dynamic_fcall && function_name->op_type==IS_CONST) {
opline->opcode = ZEND_DO_FCALL;
opline->op1 = *function_name;
ZVAL_LONG(&opline->op2.u.constant, zend_hash_func(Z_STRVAL(function_name->u.constant), Z_STRLEN(function_name->u.constant) + 1));
} else {
opline->opcode = ZEND_DO_FCALL_BY_NAME;
SET_UNUSED(opline->op1);
}