<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                ## 8.1 概述 什么是命名空間?從廣義上來說,命名空間是一種封裝事物的方法。在很多地方都可以見到這種抽象概念。例如,在操作系統中目錄用來將相關文件分組,對于目錄中的文件來說,它就扮演了命名空間的角色。具體舉個例子,文件 foo.txt 可以同時在目錄/home/greg 和 /home/other 中存在,但在同一個目錄中不能存在兩個 foo.txt 文件。另外,在目錄 /home/greg 外訪問 foo.txt 文件時,我們必須將目錄名以及目錄分隔符放在文件名之前得到 /home/greg/foo.txt。這個原理應用到程序設計領域就是命名空間的概念。(引用自php.net) 命名空間主要用來解決兩類問題: * 用戶編寫的代碼與PHP內部的或第三方的類、函數、常量、接口名字沖突 * 為很長的標識符名稱創建一個別名的名稱,提高源代碼的可讀性 PHP命名空間提供了一種將相關的類、函數、常量和接口組合到一起的途徑,不同命名空間的類、函數、常量、接口相互隔離不會沖突,注意:PHP命名空間只能隔離類、函數、常量和接口,不包括全局變量。 接下來的兩節將介紹下PHP命名空間的內部實現,主要從命名空間的定義及使用兩個方面分析。 ## 8.2 命名空間的定義 ### 8.2.1 定義語法 命名空間通過關鍵字namespace 來聲明,如果一個文件中包含命名空間,它必須在其它所有代碼之前聲明命名空間,除了declare關鍵字以外,也就是說除declare之外任何代碼都不能在namespace之前聲明。另外,命名空間并沒有文件限制,可以在多個文件中聲明同一個命名空間,也可以在同一文件中聲明多個命名空間。 ```php namespace com\aa; const MY_CONST = 1234; function my_func(){ /* ... */ } class my_class { /* ... */ } ``` 另外也可以通過{}將類、函數、常量封裝在一個命名空間下: ```php namespace com\aa{ const MY_CONST = 1234; function my_func(){ /* ... */ } class my_class { /* ... */ } } ``` 但是同一個文件中這兩種定義方式不能混用,下面這樣的定義將是非法的: ```php namespace com\aa{ /* ... */ } namespace com\bb; /* ... */ ``` 如果沒有定義任何命名空間,所有的類、函數和常量的定義都是在全局空間,與 PHP 引入命名空間概念前一樣。 ### 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]為通過{}方式定義時包裹的語句。 ![](https://box.kancloud.cn/fb54bc557ba1518163f5282f8a7b80a0_396x207.png) 此節點的編譯函數為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幫我們補全了類名、函數名、常量名。 ## 8.3 命名空間的使用 ### 8.3.1 基本用法 上一節我們知道了定義在命名空間中的類、函數和常量只是加上了namespace名稱作為前綴,既然是這樣那么在使用時加上同樣的前綴是否就可以了呢?答案是肯定的,比如上面那個例子:在com\aa命名空間下定義了一個常量MY_CONST,那么就可以這么使用: ```php include 'ns_define.php'; echo \com\aa\MY_CONST; ``` 這種按照實際類名、函數名、常量名使用的方式很容易理解,與普通的類型沒有差別,這種以"\"開頭使用的名稱稱之為:完全限定名稱,類似于絕對目錄的概念,使用這種名稱PHP會直接根據"\"之后的名稱去對應的符號表中查找(namespace定義時前面是沒有加"\"的,所以查找時也會去掉這個字符)。 除了這種形式的名稱之外,還有兩種形式的名稱: * __非限定名稱:__ 即沒有加任何namespace前綴的普通名稱,比如my_func(),使用這種名稱時如果當前有命名空間則會被解析為:currentnamespace\my_func,如果當前沒有命名空間則按照原始名稱my_func解析 * __部分限定名稱:__ 即包含namespace前綴,但不是以"\"開始的,比如:aa\my_func(),類似相對路徑的概念,這種名稱解析規則比較復雜,如果當前空間沒有使用use導入任何namespace那么與非限定名稱的解析規則相同,即如果當前有命名空間則會把解析為:currentnamespace\aa\my_func,否則解析為aa\my_func,使用use的情況后面再作說明 ### 8.3.2 use導入 使用一個命名空間中的類、函數、常量雖然可以通過完全限定名稱的形式訪問,但是這種方式需要在每一處使用的地方都加上完整的namespace名稱,如果將來namespace名稱變更了就需要所有使用的地方都改一遍,這將是很痛苦的一件事,為此,PHP提供了一種命名空間導入/別名的機制,可以通過use關鍵字將一個命名空間導入或者定義一個別名,然后在使用時就可以通過導入的namespace名稱最后一個域或者別名訪問,不需要使用完整的名稱,比如: ```php //ns_define.php namespace aa\bb\cc\dd; const MY_CONST = 1234; ``` 可以采用如下幾種方式使用: ```php //方式1: include 'ns_define.php'; use aa\bb\cc\dd; echo dd\MY_CONST; ``` ```php //方式2: include 'ns_define.php'; use aa\bb\cc; echo cc\dd\MY_CONST; ``` ```php //方式3: include 'ns_define.php'; use aa\bb\cc\dd as DD; echo DD\MY_CONST; ``` ```php //方式4: include 'ns_define.php'; use aa\bb\cc as CC; echo CC\dd\MY_CONST; ``` 這種機制的實現原理也比較簡單:編譯期間如果發現use語句 ,那么就將把這個use后的命名空間名稱插入一個哈希表:FC(imports),而哈希表的key就是定義的別名,如果沒有定義別名則key使用按"\"分割的最后一節,比如方式2的情況將以cc作為key,即:FC(imports)["cc"] = "aa\bb\cc\dd";接下來在使用類、函數和常量時會把名稱按"\"分割,然后以第一節為key查找FC(imports),如果找到了則將FC(imports)中保存的名稱與使用時的名稱拼接在一起,組成完整的名稱。實際上這種機制是把完整的名稱切割縮短然后緩存下來,使用時再拼接成完整的名稱,也就是內核幫我們組裝了名稱,對內核而言,最終使用的都是包括完整namespace的名稱。 ![](https://box.kancloud.cn/0ab98708134d363a5fdea2e5122bd945_692x181.png) use除了上面介紹的用法外還可以導入一個類,導入后再使用類就不需要加namespace了,例如: ```php //ns_define.php namespace aa\bb\cc\dd; class my_class { /* ... */ } ``` ```php include 'ns_define.php'; //導入一個類 use aa\bb\cc\dd\my_class; //直接使用 $obj = new my_class(); var_dump($obj); ``` use的這兩種用法實現原理是一樣的,都是在編譯時通過查找FC(imports)實現的名稱補全。從PHP 5.6起,use又提供了兩種針對函數、常量的導入,可以通過`use function xxx`及`use const xxx`導入一個函數、常量,這種用法的實現原理與上面介紹的實際是相同,只是在編譯時沒有保存到FC(imports),zend_file_context結構中的另外兩個哈希表就是在這種情況下使用的: ```c typedef struct _zend_file_context { ... //用于保存導入的類或命名空間 HashTable *imports; //用于保存導入的函數 HashTable *imports_function; //用于保存導入的常量 HashTable *imports_const; } zend_file_context; ``` 簡單總結下use的幾種不同用法: * __a.導入命名空間:__ 導入的名稱保存在FC(imports)中,編譯使用的語句時搜索此符號表進行補全 * __b.導入類:__ 導入的名稱保存在FC(imports)中,與a不同的是不會根據"\"切割后的最后一節檢索,而是直接使用類名查找 * __c.導入函數:__ 通過`use function`導入到FC(imports_function),補全時先查找FC(imports_function),如果沒有找到則繼續按照a的情況處理 * __d.導入常量:__ 通過`use const`導入到FC(imports_const),補全時先查找FC(imports_const),如果沒有找到則繼續按照a的情況處理 ```php use aa\bb; //導入namespace use aa\bb\MY_CLASS; //導入類 use function aa\bb\my_func; //導入函數 use const aa\bb\MY_CONST; //導入常量 ``` 接下來看下內核的具體實現,首先看下use的編譯: ```c void zend_compile_use(zend_ast *ast) { zend_string *current_ns = FC(current_namespace); //use的類型 uint32_t type = ast->attr; //根據類型獲取存儲哈希表:FC(imports)、FC(imports_function)、FC(imports_const) HashTable *current_import = zend_get_import_ht(type); ... //use可以同時導入多個 for (i = 0; i < list->children; ++i) { zend_ast *use_ast = list->child[i]; zend_ast *old_name_ast = use_ast->child[0]; zend_ast *new_name_ast = use_ast->child[1]; //old_name為use后的namespace名稱,new_name為as定義的別名 zend_string *old_name = zend_ast_get_str(old_name_ast); zend_string *new_name, *lookup_name; if (new_name_ast) { //如果有as別名則直接使用 new_name = zend_string_copy(zend_ast_get_str(new_name_ast)); } else { const char *unqualified_name; size_t unqualified_name_len; if (zend_get_unqualified_name(old_name, &unqualified_name, &unqualified_name_len)) { //按"\"分割,取最后一節為new_name new_name = zend_string_init(unqualified_name, unqualified_name_len, 0); } else { //名稱中沒有"\":use aa new_name = zend_string_copy(old_name); } } //如果是use const則大小寫敏感,其它用法都轉為小寫 if (case_sensitive) { lookup_name = zend_string_copy(new_name); } else { lookup_name = zend_string_tolower(new_name); } ... if (current_ns) { //如果當前是在命名空間中則需要檢查名稱是否沖突 ... } //插入FC(imports/imports_function/imports_const),key為lookup_name,value為old_name if (!zend_hash_add_ptr(current_import, lookup_name, old_name)) { ... } } } ``` 從use的編譯過程可以看到,編譯時的主要處理是把use導入的名稱以別名或最后分節為key存儲到對應的哈希表中,接下來我們看下在編譯使用類、函數、常量的語句時是如何處理的。使用的語法類型比較多,比如類的使用就有new、訪問靜態屬性、調用靜態方法等,但是不管什么語句都會經歷獲取類名、函數名、常量名這一步,類名的補全就是在這一步完成的。 __(1)補全類名__ 編譯時通過zend_resolve_class_name()方法進行類名補全,如果沒有任何namespace那么就返回原始的類名,比如編譯`new my_class()`時,首先會把"my_class"傳入該函數,如果查找FC(imports)后發現是一個use導入的類則把補全后的完整名稱返回,然后再進行后續的處理。 ```c zend_string *zend_resolve_class_name(zend_string *name, uint32_t type) { char *compound; //"namespace\xxx\類名"這種用法表示使用當前命名空間 if (type == ZEND_NAME_RELATIVE) { return zend_prefix_with_ns(name); } //完全限定的形式:new \aa\bb\my_class() if (type == ZEND_NAME_FQ || ZSTR_VAL(name)[0] == '\\') { if (ZSTR_VAL(name)[0] == '\\') { name = zend_string_init(ZSTR_VAL(name) + 1, ZSTR_LEN(name) - 1, 0); } else { zend_string_addref(name); } ... return name; } //如果當前腳本有通過use導入namespace if (FC(imports)) { compound = memchr(ZSTR_VAL(name), '\\', ZSTR_LEN(name)); if (compound) { // 1) 沒有直接導入一個類的情況,用法a //名稱中包括"\",比如:new aa\bb\my_class() size_t len = compound - ZSTR_VAL(name); //根據按"\"分割后的最后一節為key查找FC(imports) zend_string *import_name = zend_hash_find_ptr_lc(FC(imports), ZSTR_VAL(name), len); //如果找到了表示通過use導入了namespace if (import_name) { return zend_concat_names( ZSTR_VAL(import_name), ZSTR_LEN(import_name), ZSTR_VAL(name) + len + 1, ZSTR_LEN(name) - len - 1); } } else { // 2) 通過use導入一個類的情況,用法b //直接根據原始類名查找 zend_string *import_name = zend_hash_find_ptr_lc(FC(imports), ZSTR_VAL(name), ZSTR_LEN(name)); if (import_name) { return zend_string_copy(import_name); } } } //沒有使用use或沒命中任何use導入的namespace,按照基本用法處理:如果當前在一個namespace下則解釋為currentnamespace\my_class return zend_prefix_with_ns(name); } ``` 此方法除了類的名稱后還有一個type參數,這個參數是解析語法是根據使用方式確定的,共有三種類型: * __ZEND_NAME_NOT_FQ:__ 非限定名稱,也就是普通的類名,沒有加namespace,比如:new my_class() * __ZEND_NAME_RELATIVE:__ 相對名稱,強制按照當前所屬命名空間解析,使用時通過在類前加"namespace\xx",比如:new namespace\my_class(),如果當前是全局空間則等價于:new my_class,如果當前命名空間為currentnamespace,則解析為"currentnamespace\my_class" * __ZEND_NAME_FQ:__ 完全限定名稱,即以"\"開頭的 __(2)補全函數名、常量名__ 函數與常量名稱的補全操作是相同的: ```c //補全函數名稱 zend_string *zend_resolve_function_name(zend_string *name, uint32_t type, zend_bool *is_fully_qualified) { return zend_resolve_non_class_name( name, type, is_fully_qualified, 0, FC(imports_function)); } //補全常量名稱 zend_string *zend_resolve_const_name(zend_string *name, uint32_t type, zend_bool *is_fully_qualified) return zend_resolve_non_class_name( name, type, is_fully_qualified, 1, FC(imports_const)); } ``` 可以看到函數與常量最終調用同一方法處理,不同點在于傳入了各自的存儲哈希表: ```c zend_string *zend_resolve_non_class_name( zend_string *name, uint32_t type, zend_bool *is_fully_qualified, zend_bool case_sensitive, HashTable *current_import_sub ) { char *compound; *is_fully_qualified = 0; //完整名稱,直接返回,不需要補全 if (ZSTR_VAL(name)[0] == '\\') { *is_fully_qualified = 1; return zend_string_init(ZSTR_VAL(name) + 1, ZSTR_LEN(name) - 1, 0); } //與類的用法相同 if (type == ZEND_NAME_RELATIVE) { *is_fully_qualified = 1; return zend_prefix_with_ns(name); } //current_import_sub如果是函數則為FC(imports_function),否則為FC(imports_const) if (current_import_sub) { //查找FC(imports_function)或FC(imports_const) ... } //查找FC(imports) compound = memchr(ZSTR_VAL(name), '\\', ZSTR_LEN(name)); ... return zend_prefix_with_ns(name); } ``` 可以看到,函數與常量的的補全邏輯只是優先用原始名稱去FC(imports_function)或FC(imports_const)查找,如果沒有找到再去FC(imports)中匹配。如果我們這樣導入了一個函數:`use function aa\bb\my_func;`,編譯`my_func()`會在FC(imports_function)中根據"my_func"找到"aa\bb\my_func",從而使用完整的這個名稱。 ### 8.3.3 動態用法 前面介紹的這些命名空間的使用都是名稱為CONST類型的情況,所有的處理都是在編譯環節完成的,PHP是動態語言,能否動態使用命名空間呢?舉個例子: ```php $class_name = "\aa\bb\my_class"; $obj = new $class_name; ``` 如果類似這樣的用法只能只用完全限定名稱,也就是按照實際存儲的名稱使用,無法進行自動名稱補全。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看