国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

PHP 自動加載 深度總結

tuniutech / 1184人閱讀

摘要:將修改如下直接包含重復包含包含不存在的文件結果結論成功,返回值重復包含,返回,并忽略此次包含總結成功時,都會返回,差別在于包含失敗重復包含的處理嘗試加載未定義的類。

寫在前邊

我們將介紹 include* require* 的一些使用細節,
以及從 PHP 應用 和 zend 源碼角度,來分別分析 __autoload spl_autoload_register 的實現和調用過程。
分析的目的更多的是讓自己對這些細節加深認識,并進一步深入了解 Zend 源碼。

PHP 版本:`php-5.6`
核心方法:` spl_autoload_register`

類加載方式

手動加載

__autoload

spl_autoload_register

手動加載

包含:include include_once requice requice_one

include

以下文檔也適用于 require。
被包含文件先按參數給出的路徑尋找,如果沒有給出目錄(只有文件名)時則按照 include_path 指定的目錄尋找。如果在 include_path 下沒找到該文件則 include 最后才在調用腳本文件所在的目錄和當前工作目錄下尋找。如果最后仍未找到文件則 include 結構會發出一條警告;這一點和 require 不同,后者會發出一個致命錯誤。

如果定義了路徑——不管是絕對路徑(在 Windows 下以盤符或者 開頭,在 Unix/Linux 下以 / 開頭)還是當前目錄的相對路徑(以 . 或者 .. 開頭)——include_path 都會被完全忽略。例如一個文件以 ../ 開頭,則解析器會在當前目錄的父目錄下尋找該文件。

代碼示例:
FILE: run.php


FILE: class.php


結果:

結論:

include 成功返回值:1,失敗返回值:false

include 失敗會有 warning,不會中斷進程

include_once

include_once 行為和 include 語句類似,唯一區別是如果該文件中已經被包含過,則不會再次包含。

run.php 修改如下:


結果:

結論:

include_once 重復包含時,會直接返回 1,并忽略此次包含操作,繼續執行

require

requireinclude 幾乎完全一樣,但 require 在出錯時產生 E_COMPILE_ERROR 級別的錯誤。(腳本將會中止運行)

run.php 修改如下:


結果:

結論:

require 包含成功,同 include 一樣,返回值:1

require 包含失敗,直接拋出 Fatal error,進程中止

require_once

require_once 語句和 require 語句完全相同,唯一區別是 PHP 會檢查該文件是否已經被包含過,如果是則不會再次包含。

run.php 修改如下:

ini_set("display_errors", "1");

// 直接包含
$ret = require_once "class.php";
echo sprintf("include ret-value:%d,ret-type:%s
", $ret, gettype($ret));
Foo::getFoo();

// 重復包含
$ret2 = require_once "./class.php";
echo sprintf("include ret-value:%d,ret-type:%s
", $ret, gettype($ret));
Foo::getFoo();

// 包含不存在的文件
$ret1 = require_once "./class1.php";
echo sprintf("include ret-value:%d,ret-type:%s
", $ret1, gettype($ret1));

結果:

結論:

成功,返回值:1

重復包含,返回 1,并忽略此次包含

總結

include include_once requice requice_one 成功時,都會返回 1,差別在于 包含失敗、重復包含 的處理

__autoload

嘗試加載未定義的類。此函數將會在 PHP 7.2.0 中棄用。

PHP 代碼解釋

使用示例:
FILE:foo.php


FILE:run.php


結果:

?  load git:(master) ? php run.php
I am foo!

結論:
遇到未包含的類,會觸發 __autoload 進行加載,如果所有加載規則中沒有此類,則 Fatal error

Zend 代碼解釋

下面,我們來看一下 Zend 引擎是如何觸發 __autoload 調用的。
利用 vld 來查看剛才執行過程中產生的 opcode,結果如下:

我們看到,PHP 運行到第 10 行時,所生成的 opcode 為:INIT_STATIC_METHOD_CALL,兩個操作數都為常量(CONST)。
根據 opcode 的處理函數對應規則,我們利用 命名法 可以確定,
處理函數為:ZEND_INIT_STATIC_METHOD_CALL_SPEC_CONST_CONST_HANDLER
源碼位置為:vim Zend/zend_vm_execute.h +3819
源碼如下:

static int ZEND_FASTCALL  ZEND_INIT_STATIC_METHOD_CALL_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    USE_OPLINE
    zval *function_name;
    zend_class_entry *ce;
    call_slot *call = EX(call_slots) + opline->result.num;

    SAVE_OPLINE();

    if (IS_CONST == IS_CONST) {
        /* no function found. try a static method in class */
        if (CACHED_PTR(opline->op1.literal->cache_slot)) {
            ce = CACHED_PTR(opline->op1.literal->cache_slot);
        } else {
            ce = zend_fetch_class_by_name(Z_STRVAL_P(opline->op1.zv), Z_STRLEN_P(opline->op1.zv), opline->op1.literal + 1, opline->extended_value TSRMLS_CC);
            if (UNEXPECTED(EG(exception) != NULL)) {
                HANDLE_EXCEPTION();
            }
            if (UNEXPECTED(ce == NULL)) {
                zend_error_noreturn(E_ERROR, "Class "%s" not found", Z_STRVAL_P(opline->op1.zv));
            }
            CACHE_PTR(opline->op1.literal->cache_slot, ce);
        }
        call->called_scope = ce;
    } else {
        ce = EX_T(opline->op1.var).class_entry;

        if (opline->extended_value == ZEND_FETCH_CLASS_PARENT || opline->extended_value == ZEND_FETCH_CLASS_SELF) {
            call->called_scope = EG(called_scope);
        } else {
            call->called_scope = ce;
        }
    }

    if (IS_CONST == IS_CONST &&
        IS_CONST == IS_CONST &&
        CACHED_PTR(opline->op2.literal->cache_slot)) {
        call->fbc = CACHED_PTR(opline->op2.literal->cache_slot);
    } else if (IS_CONST != IS_CONST &&
               IS_CONST == IS_CONST &&
               (call->fbc = CACHED_POLYMORPHIC_PTR(opline->op2.literal->cache_slot, ce))) {
        /* do nothing */
    } else if (IS_CONST != IS_UNUSED) {
        char *function_name_strval = NULL;
        int function_name_strlen = 0;


        if (IS_CONST == IS_CONST) {
            function_name_strval = Z_STRVAL_P(opline->op2.zv);
            function_name_strlen = Z_STRLEN_P(opline->op2.zv);
        } else {
            function_name = opline->op2.zv;

            if (UNEXPECTED(Z_TYPE_P(function_name) != IS_STRING)) {
                if (UNEXPECTED(EG(exception) != NULL)) {
                    HANDLE_EXCEPTION();
                }
                zend_error_noreturn(E_ERROR, "Function name must be a string");
            } else {
                function_name_strval = Z_STRVAL_P(function_name);
                function_name_strlen = Z_STRLEN_P(function_name);
             }
        }

        if (function_name_strval) {
            if (ce->get_static_method) {
                call->fbc = ce->get_static_method(ce, function_name_strval, function_name_strlen TSRMLS_CC);
            } else {
                call->fbc = zend_std_get_static_method(ce, function_name_strval, function_name_strlen, ((IS_CONST == IS_CONST) ? (opline->op2.literal + 1) : NULL) TSRMLS_CC);
            }
            if (UNEXPECTED(call->fbc == NULL)) {
                zend_error_noreturn(E_ERROR, "Call to undefined method %s::%s()", ce->name, function_name_strval);
            }
            if (IS_CONST == IS_CONST &&
                EXPECTED(call->fbc->type <= ZEND_USER_FUNCTION) &&
                EXPECTED((call->fbc->common.fn_flags & (ZEND_ACC_CALL_VIA_HANDLER|ZEND_ACC_NEVER_CACHE)) == 0)) {
                if (IS_CONST == IS_CONST) {
                    CACHE_PTR(opline->op2.literal->cache_slot, call->fbc);
                } else {
                    CACHE_POLYMORPHIC_PTR(opline->op2.literal->cache_slot, ce, call->fbc);
                }
            }
        }
        if (IS_CONST != IS_CONST) {

        }
    } else {
        if (UNEXPECTED(ce->constructor == NULL)) {
            zend_error_noreturn(E_ERROR, "Cannot call constructor");
        }
        if (EG(This) && Z_OBJCE_P(EG(This)) != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) {
            zend_error_noreturn(E_ERROR, "Cannot call private %s::__construct()", ce->name);
        }
        call->fbc = ce->constructor;
    }

    if (call->fbc->common.fn_flags & ZEND_ACC_STATIC) {
        call->object = NULL;
    } else {
        if (EG(This) &&
            Z_OBJ_HT_P(EG(This))->get_class_entry &&
            !instanceof_function(Z_OBJCE_P(EG(This)), ce TSRMLS_CC)) {
            /* We are calling method of the other (incompatible) class,
               but passing $this. This is done for compatibility with php-4. */
            if (call->fbc->common.fn_flags & ZEND_ACC_ALLOW_STATIC) {
                zend_error(E_DEPRECATED, "Non-static method %s::%s() should not be called statically, assuming $this from incompatible context", call->fbc->common.scope->name, call->fbc->common.function_name);
            } else {
                /* An internal function assumes $this is present and won"t check that. So PHP would crash by allowing the call. */
                zend_error_noreturn(E_ERROR, "Non-static method %s::%s() cannot be called statically, assuming $this from incompatible context", call->fbc->common.scope->name, call->fbc->common.function_name);
            }
        }
        if ((call->object = EG(This))) {
            Z_ADDREF_P(call->object);
            call->called_scope = Z_OBJCE_P(call->object);
        }
    }

    call->num_additional_args = 0;
    call->is_ctor_call = 0;
    EX(call) = call;

    CHECK_EXCEPTION();
    ZEND_VM_NEXT_OPCODE();
}

通過以上源碼,我們發現關鍵方法為 zend_fetch_class_by_name,跟進此方法:

zend_class_entry *zend_fetch_class_by_name(const char *class_name, uint class_name_len, const zend_literal *key, int fetch_type TSRMLS_DC) /* {{{ */
{
    zend_class_entry **pce;
    int use_autoload = (fetch_type & ZEND_FETCH_CLASS_NO_AUTOLOAD) == 0;

    if (zend_lookup_class_ex(class_name, class_name_len, key, use_autoload, &pce TSRMLS_CC) == FAILURE) {
        if (use_autoload) {
            if ((fetch_type & ZEND_FETCH_CLASS_SILENT) == 0 && !EG(exception)) {
                if ((fetch_type & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_INTERFACE) {
                    zend_error(E_ERROR, "Interface "%s" not found", class_name);
                } else if ((fetch_type & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_TRAIT) {
                    zend_error(E_ERROR, "Trait "%s" not found", class_name);
                } else {
                    zend_error(E_ERROR, "Class "%s" not found", class_name);
                }
            }
        }
        return NULL;
    }
    return *pce;
}

我們發現是通過 zend_lookup_class_ex 來獲取類,繼續跟進:

ZEND_API int zend_lookup_class_ex(const char *name, int name_length, const zend_literal *key, int use_autoload, zend_class_entry ***ce TSRMLS_DC) /* {{{ */
{
    ...

    /* 注意:在 類的符號表 中沒有找到示例中調用的類 foo */
    if (zend_hash_quick_find(EG(class_table), lc_name, lc_length, hash, (void **) ce) == SUCCESS) {
        if (!key) {
            free_alloca(lc_free, use_heap);
        }
        return SUCCESS;
    }

    ...

    /*
     * ZVAL_STRINGL 為 zval (即 PHP 類型的實現基礎 zvalue_value)賦值宏,
     * 此處實現了 把 ZEND_AUTOLOAD_FUNC_NAME 值 賦給 autoload_function
     * #define ZEND_AUTOLOAD_FUNC_NAME     "__autoload"
     */
    ZVAL_STRINGL(&autoload_function, ZEND_AUTOLOAD_FUNC_NAME, sizeof(ZEND_AUTOLOAD_FUNC_NAME) - 1, 0);

    ...

    fcall_info.size = sizeof(fcall_info);
    fcall_info.function_table = EG(function_table);
    fcall_info.function_name = &autoload_function;
    fcall_info.symbol_table = NULL;
    fcall_info.retval_ptr_ptr = &retval_ptr;
    fcall_info.param_count = 1;
    fcall_info.params = args;
    fcall_info.object_ptr = NULL;
    fcall_info.no_separation = 1;

    fcall_cache.initialized = EG(autoload_func) ? 1 : 0;
    fcall_cache.function_handler = EG(autoload_func); /* 留意此處 */
    fcall_cache.calling_scope = NULL;
    fcall_cache.called_scope = NULL;
    fcall_cache.object_ptr = NULL;

    ...

    retval = zend_call_function(&fcall_info, &fcall_cache TSRMLS_CC); /* 調用自動加載函數 */

    ...

    EG(autoload_func) = fcall_cache.function_handler;

    zval_ptr_dtor(&class_name_ptr);

    zend_hash_quick_del(EG(in_autoload), lc_name, lc_length, hash);

    ...
}

我們發現是通過 zend_call_function 出發了自動加載函數,而且看到了加載方法的名字 __autoload (宏:ZEND_AUTOLOAD_FUNC_NAME

zend_call_function 中會做一下檢測并調用等,而且我們看到 zend_lookup_class_ex 的返回結果即為 zend_call_function 的返回結果。

接下來我們逐步退出函數調用棧:
假設 zend_call_function 調用失敗,返回 FALSE
zend_lookup_class_ex 返回 FALSE
zend_fetch_class_by_name 返回 NULL
ZEND_INIT_STATIC_METHOD_CALL_SPEC_CONST_CONST_HANDLER 拋出異常 Class ** not found,如下圖所示:

結論

至此,我們通過 PHP 代碼 Zend 源碼 了解了 __autoload 的調用過程。
我們知道 __autoload 現在已并不推薦使用,
它的缺點也很明顯,不支持多個自動加載函數。

spl_autoload_register

將函數注冊到SPL __autoload函數隊列中。如果該隊列中的函數尚未激活,則激活它們。如果在你的程序中已經實現了__autoload()函數,它必須顯式注冊到__autoload()隊列中。

PHP 代碼解釋

使用示例:
FILE:foo.php
(同上 __autoload)

FILE:foo2.class.php


FILE:run.php


結果如下:

我們看到,調用 getFoo2 時,會先調用第一個注冊的 autoload 方法,如果沒找到對應的類,會產生 warning 并繼續調用后邊注冊的 autoload 方法。
說明了 PHP 內核中為通過 spl_autoload_register 注冊的 autoload 方法維護了一個隊列,當前文件為包含調用類,便會觸發此隊列,并依次調用,直到隊列結束 或者 找到對應類。

Zend 源碼解釋

首先,我們看一下 PHP 文件生成的 opcode

我們發現,其方法調用所生成的 opcode__autoload 一樣,
但是我們之前調用了 `spl_autoload_register,
那么,看一下 spl_autoload_register 的源碼:
FILE: ext/spl/php_spl.c*

PHP_FUNCTION(spl_autoload_register)
{
    char *func_name, *error = NULL;
    int  func_name_len;
    char *lc_name = NULL;
    zval *zcallable = NULL;
    zend_bool do_throw = 1;
    zend_bool prepend  = 0;
    zend_function *spl_func_ptr;
    autoload_func_info alfi;
    zval *obj_ptr;
    zend_fcall_info_cache fcc;

    if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, "|zbb", &zcallable, &do_throw, &prepend) == FAILURE) {
        return;
    }

    if (ZEND_NUM_ARGS()) {
        if (Z_TYPE_P(zcallable) == IS_STRING) {
            if (Z_STRLEN_P(zcallable) == sizeof("spl_autoload_call") - 1) {
                if (!zend_binary_strcasecmp(Z_STRVAL_P(zcallable), sizeof("spl_autoload_call"), "spl_autoload_call", sizeof("spl_autoload_call"))) {
                    if (do_throw) {
                        zend_throw_exception_ex(spl_ce_LogicException, 0 TSRMLS_CC, "Function spl_autoload_call() cannot be registered");
                    }
                    RETURN_FALSE;
                }
            }
        }
    
        if (!zend_is_callable_ex(zcallable, NULL, IS_CALLABLE_STRICT, &func_name, &func_name_len, &fcc, &error TSRMLS_CC)) {
            alfi.ce = fcc.calling_scope;
            alfi.func_ptr = fcc.function_handler;
            obj_ptr = fcc.object_ptr;
            if (Z_TYPE_P(zcallable) == IS_ARRAY) {
                if (!obj_ptr && alfi.func_ptr && !(alfi.func_ptr->common.fn_flags & ZEND_ACC_STATIC)) {
                    if (do_throw) {
                        zend_throw_exception_ex(spl_ce_LogicException, 0 TSRMLS_CC, "Passed array specifies a non static method but no object (%s)", error);
                    }
                    if (error) {
                        efree(error);
                    }
                    efree(func_name);
                    RETURN_FALSE;
                }
                else if (do_throw) {
                    zend_throw_exception_ex(spl_ce_LogicException, 0 TSRMLS_CC, "Passed array does not specify %s %smethod (%s)", alfi.func_ptr ? "a callable" : "an existing", !obj_ptr ? "static " : "", error);
                }
                if (error) {
                    efree(error);
                }
                efree(func_name);
                RETURN_FALSE;
            } else if (Z_TYPE_P(zcallable) == IS_STRING) {
                if (do_throw) {
                    zend_throw_exception_ex(spl_ce_LogicException, 0 TSRMLS_CC, "Function "%s" not %s (%s)", func_name, alfi.func_ptr ? "callable" : "found", error);
                }
                if (error) {
                    efree(error);
                }
                efree(func_name);
                RETURN_FALSE;
            } else {
                if (do_throw) {
                    zend_throw_exception_ex(spl_ce_LogicException, 0 TSRMLS_CC, "Illegal value passed (%s)", error);
                }
                if (error) {
                    efree(error);
                }
                efree(func_name);
                RETURN_FALSE;
            }
        }
        alfi.closure = NULL;
        alfi.ce = fcc.calling_scope;
        alfi.func_ptr = fcc.function_handler;
        obj_ptr = fcc.object_ptr;
        if (error) {
            efree(error);
        }
    
        lc_name = safe_emalloc(func_name_len, 1, sizeof(long) + 1);
        zend_str_tolower_copy(lc_name, func_name, func_name_len);
        efree(func_name);

        if (Z_TYPE_P(zcallable) == IS_OBJECT) {
            alfi.closure = zcallable;
            Z_ADDREF_P(zcallable);

            lc_name = erealloc(lc_name, func_name_len + 2 + sizeof(zend_object_handle));
            memcpy(lc_name + func_name_len, &Z_OBJ_HANDLE_P(zcallable),
                sizeof(zend_object_handle));
            func_name_len += sizeof(zend_object_handle);
            lc_name[func_name_len] = "