### 8.2.2 內部實現
命名空間的實現實際比較簡單,當聲明了一個命名空間后,接下來編譯類、函數和常量時會把類名、函數名和常量名統一加上命名空間的名稱作為前綴存儲,也就是說聲明在命名空間中的類、函數和常量的實際名稱是被修改過的,這樣來看他們與普通的定義方式是沒有區別的,只是這個前綴是內核幫我們自動添加的,例如:
```php
//ns_define.php
namespace com\aa;
const MY_CONST = 1234;
function my_func(){ /* ... */ }
class my_class { /* ... */ }
```
最終MY_CONST、my_func、my_class在EG(zend_constants)、EG(function_table)、EG(class_table)中的實際存儲名稱被修改為:com\aa\MY_CONST、com\aa\my_func、com\aa\my_class。
下面具體看下編譯過程,namespace語法被編譯為ZEND_AST_NAMESPACE類型的語法樹節點,它有兩個子節點:child[0]為命名空間的名稱、child[1]為通過{}方式定義時包裹的語句。

此節點的編譯函數為zend_compile_namespace():
```c
void zend_compile_namespace(zend_ast *ast)
{
zend_ast *name_ast = ast->child[0];
zend_ast *stmt_ast = ast->child[1];
zend_string *name;
zend_bool with_bracket = stmt_ast != NULL;
//檢查聲明方式,不允許{}與非{}混用
...
if (FC(current_namespace)) {
zend_string_release(FC(current_namespace));
}
if (name_ast) {
name = zend_ast_get_str(name_ast);
if (ZEND_FETCH_CLASS_DEFAULT != zend_get_class_fetch_type(name)) {
zend_error_noreturn(E_COMPILE_ERROR, "Cannot use '%s' as namespace name", ZSTR_VAL(name));
}
//將命名空間名稱保存到FC(current_namespace)
FC(current_namespace) = zend_string_copy(name);
} else {
FC(current_namespace) = NULL;
}
//重置use導入的命名空間符號表
zend_reset_import_tables();
...
if (stmt_ast) {
//如果是通過namespace xxx { ... }這種方式聲明的則直接編譯{}中的語句
zend_compile_top_stmt(stmt_ast);
zend_end_namespace();
}
}
```
從上面的編譯過程可以看出,命名空間定義的編譯過程非常簡單,最主要的操作是把FC(current_namespace)設置為當前定義的命名空間名稱,FC()這個宏為:CG(file_context),前面曾介紹過,file_context是在編譯過程中使用的一個結構:
```c
typedef struct _zend_file_context {
zend_declarables declarables;
znode implementing_class;
//當前所屬namespace
zend_string *current_namespace;
//是否在namespace中
zend_bool in_namespace;
//當前namespace是否為{}定義
zend_bool has_bracketed_namespaces;
//下面這三個值在后面介紹use時再說明,這里忽略即可
HashTable *imports;
HashTable *imports_function;
HashTable *imports_const;
} zend_file_context;
```
編譯完namespace聲明語句后接著編譯下面的語句,此后定義的類、函數、常量均屬于此命名空間,直到遇到下一個namespace的定義,接下來繼續分析下這三種類型編譯過程中有何不同之處。
__(1)編譯類、函數__
前面章節曾詳細介紹過函數、類的編譯過程,總結下主要分為兩步:第1步是編譯函數、類,這個過程將分別生成一條ZEND_DECLARE_FUNCTION、ZEND_DECLARE_CLASS的opcode;第2步是在整個腳本編譯的最后執行zend_do_early_binding(),這一步相當于執行ZEND_DECLARE_FUNCTION、ZEND_DECLARE_CLASS,函數、類正是在這一步注冊到EG(function_table)、EG(class_table)中去的。
在生成ZEND_DECLARE_FUNCTION、ZEND_DECLARE_CLASS兩條opcode時會把函數名、類名的存儲位置通過操作數記錄下來,然后在zend_do_early_binding()階段直接獲取函數名、類名作為key注冊到EG(function_table)、EG(class_table)中,定義在命名空間中的函數、類的名稱修改正是在生成ZEND_DECLARE_FUNCTION、ZEND_DECLARE_CLASS時完成的,下面以函數為例看下具體的處理:
```c
//函數的編譯方法
void zend_compile_func_decl(znode *result, zend_ast *ast)
{
...
//生成函數聲明的opcode:ZEND_DECLARE_FUNCTION
zend_begin_func_decl(result, op_array, decl);
//編譯參數、函數體
...
}
```
```c
static void zend_begin_func_decl(znode *result, zend_op_array *op_array, zend_ast_decl *decl)
{
...
//獲取函數名稱
op_array->function_name = name = zend_prefix_with_ns(unqualified_name);
lcname = zend_string_tolower(name);
if (FC(imports_function)) {
//如果通過use導入了其他命名空間則檢查函數名稱是否已存在
}
....
//生成一條opcode:ZEND_DECLARE_FUNCTION
opline = get_next_op(CG(active_op_array));
opline->opcode = ZEND_DECLARE_FUNCTION;
//函數名的存儲位置記錄在op2中
opline->op2_type = IS_CONST;
LITERAL_STR(opline->op2, zend_string_copy(lcname));
...
}
```
函數名稱通過zend_prefix_with_ns()方法獲取:
```c
zend_string *zend_prefix_with_ns(zend_string *name) {
if (FC(current_namespace)) {
//如果當前是在namespace下則拼上namespace名稱作為前綴
zend_string *ns = FC(current_namespace);
return zend_concat_names(ZSTR_VAL(ns), ZSTR_LEN(ns), ZSTR_VAL(name), ZSTR_LEN(name));
} else {
return zend_string_copy(name);
}
}
```
在zend_prefix_with_ns()方法中如果發現FC(current_namespace)不為空則將函數名加上FC(current_namespace)作為前綴,接下來向EG(function_table)注冊時就使用修改后的函數名作為key,類的情況與函數的處理方式相同,不再贅述。
__(2)編譯常量__
常量的編譯過程與函數、類基本相同,也是在編譯過程獲取常量名時檢查FC(current_namespace)是否為空,如果不為空表示常量聲明在namespace下,則為常量名加上FC(current_namespace)前綴。
總結下命名空間的定義:編譯時如果發現定義了一個namespace,則將命名空間名稱保存到FC(current_namespace),編譯類、函數、常量時先判斷FC(current_namespace)是否為空,如果為空則按正常名稱編譯,如果不為空則將類名、函數名、常量名加上FC(current_namespace)作為前綴,然后再以修改后的名稱注冊。整個過程相當于PHP幫我們補全了類名、函數名、常量名。
- 前言
- 第1章 PHP基本架構
- 1.1 PHP簡介
- 1.2 PHP7的改進
- 1.3 FPM
- 1.3.1 概述
- 1.3.2 基本實現
- 1.3.3 FPM的初始化
- 1.3.4 請求處理
- 1.3.5 進程管理
- 1.4 PHP執行的幾個階段
- 第2章 變量
- 2.1 變量的內部實現
- 2.2 數組
- 2.3 靜態變量
- 2.4 全局變量
- 2.5 常量
- 第3章 Zend虛擬機
- 3.1 PHP代碼的編譯
- 3.1.1 詞法解析、語法解析
- 3.1.2 抽象語法樹編譯流程
- 3.2 函數實現
- 3.2.1 內部函數
- 3.2.2 用戶函數的實現
- 3.3 Zend引擎執行流程
- 3.3.1 基本結構
- 3.3.2 執行流程
- 3.3.3 函數的執行流程
- 3.3.4 全局execute_data和opline
- 3.4 面向對象實現
- 3.4.1 類
- 3.4.2 對象
- 3.4.3 繼承
- 3.4.4 動態屬性
- 3.4.5 魔術方法
- 3.4.6 類的自動加載
- 3.5 運行時緩存
- 3.6 Opcache
- 3.6.1 opcode緩存
- 3.6.2 opcode優化
- 3.6.3 JIT
- 第4章 PHP基礎語法實現
- 4.1 類型轉換
- 4.2 選擇結構
- 4.3 循環結構
- 4.4 中斷及跳轉
- 4.5 include/require
- 4.6 異常處理
- 第5章 內存管理
- 5.1 Zend內存池
- 5.2 垃圾回收
- 第6章 線程安全
- 6.1 什么是線程安全
- 6.2 線程安全資源管理器
- 第7章 擴展開發
- 7.1 概述
- 7.2 擴展的實現原理
- 7.3 擴展的構成及編譯
- 7.3.1 擴展的構成
- 7.3.2 編譯工具
- 7.3.3 編寫擴展的基本步驟
- 7.3.4 config.m4
- 7.4 鉤子函數
- 7.5 運行時配置
- 7.5.1 全局變量
- 7.5.2 ini配置
- 7.6 函數
- 7.6.1 內部函數注冊
- 7.6.2 函數參數解析
- 7.6.3 引用傳參
- 7.6.4 函數返回值
- 7.6.5 函數調用
- 7.7 zval的操作
- 7.7.1 新生成各類型zval
- 7.7.2 獲取zval的值及類型
- 7.7.3 類型轉換
- 7.7.4 引用計數
- 7.7.5 字符串操作
- 7.7.6 數組操作
- 7.8 常量
- 7.9 面向對象
- 7.9.1 內部類注冊
- 7.9.2 定義成員屬性
- 7.9.3 定義成員方法
- 7.9.4 定義常量
- 7.9.5 類的實例化
- 7.10 資源類型
- 7.11 經典擴展解析
- 7.8.1 Yaf
- 7.8.2 Redis
- 第8章 命名空間
- 8.1 概述
- 8.2 命名空間的定義
- 8.2.1 定義語法
- 8.2.2 內部實現
- 8.3 命名空間的使用
- 8.3.1 基本用法
- 8.3.2 use導入
- 8.3.3 動態用法
- 附錄
- break/continue按標簽中斷語法實現
- defer推遲函數調用語法的實現
- 一起線上事故引發的對PHP超時控制的思考