<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>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ## 4.5 include/require 在實際應用中,我們不可能把所有的代碼寫到一個文件中,而是會按照一定的標準進行文件劃分,include與require的功能就是將其他文件包含進來并且執行,比如在面向對象中通常會把一個類定義在單獨文件中,使用時再include進來,類似其他語言中包的概念。 include與require沒有本質上的區別,唯一的不同在于錯誤級別,當文件無法被正常加載時include會拋出warning警告,而require則會拋出error錯誤,本節下面的內容將以include說明。 在分析include的實現過程之前,首先要明確include的基本用法及特點: * 被包含的文件將繼承include所在行具有的全部變量范圍,比如調用文件前面定義了一些變量,那么這些變量就能夠在被包含的文件中使用,反之,被包含文件中定義的變量也將從include調用處開始可以被被調用文件所使用。 * 被包含文件中定義的函數、類在include執行之后將可以被隨處使用,即具有全局作用域。 * include是在運行時加載文件并執行的,而不是在編譯時。 這幾個特性可以理解為include就是把其它文件的內容拷貝到了調用文件中,類似C語言中的宏(當然執行的時候并不是這樣),舉個例子來說明: ```php //a.php $var_1 = "hi"; $var_2 = array(1,2,3); include 'b.php'; var_dump($var_2); var_dump($var_3); //b.php $var_2 = array(); $var_3 = 9; ``` 執行`php a.php`結果顯示$var_2值被修改為array()了,而include文件中新定義的$var_3也可以在調用文件中使用。 接下來我們就以這個例子詳細介紹下include具體是如何實現的。 ![](https://box.kancloud.cn/d9c4996f3723b6cde73658ed06145ccd_863x151.png) 前面我們曾介紹過Zend引擎的編譯、執行兩個階段(見上圖),整個過程的輸入是一個文件,然后經過`PHP代碼->AST->Opcodes->execute`一系列過程完成整個處理,編譯過程的輸入是一個文件,輸出是zend_op_array,輸出接著成為執行過程的輸入,而include的處理實際就是這個過程,執行include時把被包含的文件像主腳本一樣編譯然后執行,接著在回到調用處繼續執行。 ![](https://box.kancloud.cn/cf6705fc53b2b2b35de9c72290247425_459x242.png) include的編譯過程非常簡單,只編譯為一條opcode:`ZEND_INCLUDE_OR_EVAL`,下面看下其具體處理過程: ```c static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INCLUDE_OR_EVAL_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { //include文件編譯的zend_op_array zend_op_array *new_op_array=NULL; zval *inc_filename; zval tmp_inc_filename; zend_bool failure_retval=0; SAVE_OPLINE(); inc_filename = EX_CONSTANT(opline->op1); ... switch (opline->extended_value) { ... case ZEND_INCLUDE: case ZEND_REQUIRE: //編譯include的文件 new_op_array = compile_filename(opline->extended_value, inc_filename); break; ... } ... zend_execute_data *call; //分配運行時的zend_execute_data call = zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_CODE, (zend_function*)new_op_array, 0, EX(called_scope), Z_OBJ(EX(This))); //繼承調用文件的全局變量符號表 if (EX(symbol_table)) { call->symbol_table = EX(symbol_table); } else { call->symbol_table = zend_rebuild_symbol_table(); } //保存當前zend_execute_data,include執行完再還原 call->prev_execute_data = execute_data; //執行前初始化 i_init_code_execute_data(call, new_op_array, return_value); //zend_execute_ex執行器入口,如果沒有自定義這個函數則默認為execute_ex() if (EXPECTED(zend_execute_ex == execute_ex)) { //將執行器切到新的zend_execute_data,回憶下execute_ex()中的切換過程 ZEND_VM_ENTER(); } ... } ``` 整個過程比較容易理解,編譯的過程不再重復,與之前介紹的沒有差別;執行的過程實際非常像函數的調用過程,首先也是重新分配了一個zend_execute_data,然后將執行器切到新的zend_execute_data,執行完以后再切回調用處,如果include文件中只定義了函數、類,沒有定義全局變量則執行過程實際直接執行return,只是在編譯階段將函數、類注冊到EG(function_table)、EG(class_table)中了,這種情況比較簡單,但是如果有全局變量定義處理就比較復雜了,比如上面那個例子,兩個文件中都定義了全局變量,這些變量是如何被繼承、合并的呢? 上面的過程中還有一個關鍵操作:`i_init_code_execute_data()`,關于這個函數在前面介紹`zend_execute()`時曾提過,這里面除了一些上下文的設置還會把當前zend_op_array下的變量移到EG(symbol_table)全局變量符號表中去,這些變量相對自己的作用域是局部變量,但它們定義在函數之外,實際也是全局變量,可以在函數中通過global訪問,在執行前會把所有在php中定義的變量(zend_op_array->vars數組)插入EG(symbol_table),value指向zend_execute_data局部變量的zval,如下圖: ![](https://box.kancloud.cn/f987ea23edefcf51d2333d3deeee3fe2_739x206.png) 而include時也會執行這個步驟,如果發現var已經在EG(symbol_table)存在了,則會把value重新指向新的zval,也就是被包含文件的zend_execute_data的局部變量,同時會把原zval的value"拷貝"給新zval的value,概括一下就是被包含文件中的變量會繼承、覆蓋調用文件中的變量,這就是為什么被包含文件中可以直接使用調用文件中定義的變量的原因。被包含文件在`zend_attach_symbol_table()`完成以后EG(symbole_table)與zend_execute_data的關系: ![](https://box.kancloud.cn/12df2cf1e1935052e35048c43374b7f9_733x364.png) > 注意:這里include文件中定義的var_2實際是替換了原文件中的變量,也就是只有一個var_2,所以此處zend_array的引用是1而不是2 接下來就是被包含文件的執行,執行到`$var_2 = array()`時,將原array(1,2,3)引用減1變為0,這時候將其釋放,然后將新的value:array()賦給$var_2,這個過程就是普通變量的賦值過程,注意此時調用文件中的$var_2仍然指向被釋放掉的value,此時的內存關系: ![](https://box.kancloud.cn/f2dc6a5144260fdfb2ba24e52fb4486b_730x390.png) 看到這里你可能會有一個疑問:$var_2既然被重新修改為新的一個值了,那么為什么調用文件中的$var_2仍然指向釋放掉的value呢?include執行完成回到原來的調用文件中后為何可以讀取到新的$var_2值以及新定義的var_3呢?答案在被包含文件執行完畢return的過程中。 被包含文件執行完以后最后執行return返回調用文件include的位置,return時會把***被包含文件中的***全局變量從zend_execute_data中移到EG(symbol_table)中,這里的移動是把value值更新到EG(symbol_table),而不是像原來那樣間接的指向value,這個操作在`zend_detach_symbol_table()`中完成,具體的return處理: ```c static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS) { ... if (EXPECTED((ZEND_CALL_KIND_EX(call_info) & ZEND_CALL_TOP) == 0)) { //將include文件中定義的變量移到EG(symbol_table) zend_detach_symbol_table(execute_data); //釋放zend_op_array destroy_op_array(&EX(func)->op_array); old_execute_data = execute_data; //切回調用文件的zend_execute_data execute_data = EG(current_execute_data) = EX(prev_execute_data); //釋放include文件的zend_execute_data zend_vm_stack_free_call_frame_ex(call_info, old_execute_data); //重新attach zend_attach_symbol_table(execute_data); LOAD_NEXT_OPLINE(); ZEND_VM_LEAVE(); }else{ //函數、主腳本返回的情況 } } ``` `zend_detach_symbol_table()`操作: ```c ZEND_API void zend_detach_symbol_table(zend_execute_data *execute_data) { zend_op_array *op_array = &execute_data->func->op_array; HashTable *ht = execute_data->symbol_table; /* copy real values from CV slots into symbol table */ if (EXPECTED(op_array->last_var)) { zend_string **str = op_array->vars; zend_string **end = str + op_array->last_var; zval *var = EX_VAR_NUM(0); do { if (Z_TYPE_P(var) == IS_UNDEF) { zend_hash_del(ht, *str); } else { zend_hash_update(ht, *str, var); ZVAL_UNDEF(var); } str++; var++; } while (str != end); } } ``` 完成以后EG(symbol_table): ![](https://box.kancloud.cn/88dac8313c3f1e0c6a6efa2b192e9ab5_730x278.png) 接著是還原調用文件的zend_execute_data,切回調用文件的include位置,在將執行器切回之前再次執行了`zend_attach_symbol_table()`,這時就會將原調用文件的變量重新插入全局變量符號表,插入$var_2、$var_3時發現已經存在了,則將局部變量區的$var_2、$var_3的value修改為這個值,這就是$var_2被include文件更新后覆蓋原value的過程,同時$var_3也因為在調用文件中出現了所以值被修改為include中設定的值,此時的內存關系: ![](https://box.kancloud.cn/2e1647863da33f0fd8dfb2b56268e457_729x233.png) 這就是include的實現原理,整個過程并不復雜,比較難理解的一點在于兩個文件之間變量的繼承、覆蓋,可以仔細研究下上面不同階段時的內存關系圖。 最后簡單介紹下include_once、require_once,這兩個與include、require的區別是在一次請求中同一文件只會被加載一次,第一次執行時會把這個文件保存在EG(included_files)哈希表中,再次加載時檢查這個哈希表,如果發現已經加載過則直接跳過。
                  <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>

                              哎呀哎呀视频在线观看