## 介紹
在PHP中,函數分為倆種:
一種是zend_internal_function, 這種函數是由擴展或者Zend/PHP內核提供的,用’C/C++’編寫的,可以直接執行的函數.
另外一種是zend_user_function, 這種函數呢,就是我們經常在見的,用戶在PHP腳本中定義的函數,這種函數最終會被ZE翻譯成opcode array來執行
zval : zend_function func 類型成員
EG(function_table)是一個哈希表,記錄的就是PHP中所有的函數.
zend_internal_function,zend_function,zend_op_array這三種結構在一定程序上存在公共的元素, 于是這些元素以聯合體的形式共享內存,并且在執行過程中對于一個函數,這三種結構對應的字段在值上都是一樣的, 于是可以在一些結構間發生完美的強制類型轉換.zend_op_array與zend_internal_function結構的起始位置都有common中的幾個成員,common可以看作是op_array、internal_function的header,不管是什么哪種函數都可以通過zend_function.common.xx快速訪問,zend_function可以與zend_op_array互換,zend_function可以與zend_internal_function互換,但是一個zend_op_array結構轉換成zend_function是不能再次轉變成zend_internal_function結構的,反之亦然.
### 結構
```c
//zend_compile.h
union _zend_function {
zend_uchar type; /* 函數類型 */
uint32_t quick_arg_flags;
struct {
zend_uchar type; /* never used */
zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */
uint32_t fn_flags; //作為方法時的訪問類型等,如ZEND_ACC_STATIC等
zend_string *function_name; //函數名稱
zend_class_entry *scope; //成員方法所屬類,面向對象實現中用到
union _zend_function *prototype;//函數原型
uint32_t num_args; //參數數量
uint32_t required_num_args; //必傳參數數量
zend_arg_info *arg_info; //參數信息
} common;
zend_op_array op_array; //函數實際編譯為普通的zend_op_array
zend_internal_function internal_function;
};
//內部函數結構
typedef struct _zend_internal_function {
/* Common elements */
zend_uchar type;
zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */
uint32_t fn_flags;
zend_string* function_name;
zend_class_entry *scope;
zend_function *prototype;
uint32_t num_args;
uint32_t required_num_args;
zend_internal_arg_info *arg_info;
/* END of common elements */
void (*handler)(INTERNAL_FUNCTION_PARAMETERS); //函數指針,展開:void (*handler)(zend_execute_data *execute_data, zval *return_value)
struct _zend_module_entry *module;
void *reserved[ZEND_MAX_RESERVED_RESOURCES];
} zend_internal_function;
//用戶自定義函數結構
struct _zend_op_array {
/* Common elements common是普通函數或類成員方法對應的opcodes快速訪問時使用的字段*/
zend_uchar type;
zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */
uint32_t fn_flags;
zend_string *function_name;
zend_class_entry *scope;
zend_function *prototype;
uint32_t num_args;
uint32_t required_num_args;
zend_arg_info *arg_info;
/* END of common elements */
uint32_t *refcount;
uint32_t last;
zend_op *opcodes; //opcode指令數組
int last_var; //PHP代碼里定義的變量數:op_type為IS_CV的變量,不含IS_TMP_VAR、IS_VAR的,編譯前0,然后發現一個新變量這個值就加1
uint32_t T; //臨時變量數:op_type為IS_TMP_VAR、IS_VAR的變量
zend_string **vars; //這個數組在ast編譯期間配合last_var用來確定各個變量的編號,非常重要的一步操作//PHP變量名數組
...
HashTable *static_variables; //靜態變量符號表:通過static聲明的
...
int last_literal; //字面量數量
zval *literals; //字面量(常量)數組,這些都是在PHP代碼定義的一些值
int cache_size; //運行時緩存數組大小
void **run_time_cache; //運行時緩存,主要用于緩存一些znode_op以便于快速獲取數據,后面單獨介紹這個機制
void *reserved[ZEND_MAX_RESERVED_RESOURCES];
};
//函數類型
#define ZEND_INTERNAL_FUNCTION 1 //內置的函數
#define ZEND_USER_FUNCTION 2 //用戶函數
#define ZEND_OVERLOADED_FUNCTION 3 //對象中__call相關
#define ZEND_EVAL_CODE 4 //eval code
#define ZEND_OVERLOADED_FUNCTION_TEMPORARY 5 //對象中__call相關
```
## 用戶自定義函數
PHP在編譯階段將用戶自定義的函數編譯為獨立的opcodes,保存在EG(function_table)中,調用時重新分配新的zend_execute_data(相當于運行棧),然后執行函數的opcodes,調用完再還原到舊的zend_execute_data,繼續執行,關于zend引擎execute階段后面會詳細分析.
zend_function的結構中的op_array存儲了該函數中所有的操作,當函數被調用時,ZE就會將這個op_array中的opline一條條順次執行, 并將最后的返回值返回. 從VLD擴展中查看的關于函數的信息可以看出,函數的定義和執行是分開的,一個函數可以作為一個獨立的運行單元而存在.
### 函數參數
參數名稱也在zend_op_array.vars中,編號首先是從參數開始的,所以按照參數順序其編號依次為0、1、2...
參數的其它信息通過zend_arg_info結構記錄:
```c
typedef struct _zend_arg_info {
zend_string *name; //參數名
zend_string *class_name;//類名
zend_uchar type_hint; //顯式聲明的參數類型,比如(array $param_1)
zend_uchar pass_by_reference; //是否引用傳參,參數前加&的這個值就是1
zend_bool allow_null; //是否允許為NULL,注意:這個值并不是用來表示參數是否為必傳的
zend_bool is_variadic; //是否為可變參數,即...用法,與golang的用法相同,5.6以上新增的一個用法:function my_func($a, ...$b){...}
} zend_arg_info;
```
每個參數都有一個上面的結構,所有參數的結構保存在zend_op_array.arg_info數組中,這里有一個地方需要注意:zend_op_array->arg_info數組保存的并不全是輸入參數,如果函數聲明了返回值類型則也會為它創建一個zend_arg_info,這個結構在arg_info數組的第一個位置,這種情況下zend_op_array->arg_info指向的實際是數組的第二個位置,返回值的結構通過zend_op_array->arg_info[-1]讀取.
### 編譯過程
zend引擎編譯課程再講解
## 內部函數
內部函數的定義非常簡單,我們只需要創建一個普通的C函數,然后創建一個zend_internal_function結構添加到 EG(function_table)
### 解析函數參數
ZEND_API int zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, ...)
第一個參數num_args表明表示想要接收的參數個數,我們經常使用ZEND_NUM_ARGS() 來表示對傳入的參數“有多少要多少”.
第二參數應該總是宏 TSRMLS_CC .
第三個參數 type_spec 是一個字符串,用來指定我們所期待接收的各個參數的類型,有點類似于 printf 中指定輸出格式的那個格式化字符串.
剩下的參數就是我們用來接收PHP參數值的變量的指針.
### PHP自帶函數講解
```c
// ext/standard/string.c : line 2744
static void php_ucfirst(char *str)
{
register char *r;
r = str;
*r = toupper((unsigned char) *r);
}
PHP_FUNCTION(ucfirst)
{
zend_string *str;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_STR(str)
ZEND_PARSE_PARAMETERS_END();
if (!ZSTR_LEN(str)) {
RETURN_EMPTY_STRING();
}
ZVAL_STRINGL(return_value, ZSTR_VAL(str), ZSTR_LEN(str));
php_ucfirst(Z_STRVAL_P(return_value));
}
// ext/standard/php_string.h
PHP_FUNCTION(ucfirst);
```
### 注冊新函數
在擴展課程再講解
### 函數注冊過程
## 測試
```php
<?php
/*
//列出所有函數
function test(){
print_r(get_defined_functions());
}
test();
*/
/* 調用用戶定義函數
function test(){}
test();
*/
/* 調用內部函數 */
strtoupper('test');
```
# 函數
## 分類
## zval function
## 結構
# 內部函數
## 擴展結構
# opcode
# 編譯過程示例
## 參考資料:
http://www.laruence.com/
https://github.com/pangudashu/php7-internal/
http://www.php-internals.com/book