PHP 自動加載 深度總結

寫在前邊

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

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

類加載方式

  • 手動加載
  • __autoload
  • spl_autoload_register

手動加載

包含:include include_once requice requice_onegit

include

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

若是定義了路徑——無論是絕對路徑(在 Windows 下以盤符或者 開頭,在 Unix/Linux 下以 / 開頭)仍是當前目錄的相對路徑(以 . 或者 .. 開頭)——include_path 都會被徹底忽略。例如一個文件以 ../ 開頭,則解析器會在當前目錄的父目錄下尋找該文件。函數

代碼示例:
FILE: run.phpfetch

<?php
ini_set('display_errors', '1');

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

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

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

FILE: class.phpui

<?php

class Foo
{
    static public function getFoo()
    {
        echo "I am foo!\n";
    }
}

結果:
clipboard.pngthis

結論:spa

  1. include 成功返回值:1,失敗返回值:false
  2. include 失敗會有 warning,不會中斷進程

include_once

include_once 行爲和 include 語句相似,惟一區別是若是該文件中已經被包含過,則不會再次包含。.net

run.php 修改以下:rest

<?php
ini_set('display_errors', '1');

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

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

結果:
clipboard.png

結論:

  1. include_once 重複包含時,會直接返回 1,並忽略這次包含操做,繼續執行

require

requireinclude 幾乎徹底同樣,但 require 在出錯時產生 E_COMPILE_ERROR 級別的錯誤。(腳本將會停止運行)

run.php 修改以下:

<?php
ini_set('display_errors', '1');

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

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

結果:
clipboard.png

結論:

  1. require 包含成功,同 include 同樣,返回值:1
  2. 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\n", $ret, gettype($ret));
Foo::getFoo();

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

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

結果:
clipboard.png

結論:

  1. 成功,返回值:1
  2. 重複包含,返回 1,並忽略這次包含

總結

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

__autoload

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

PHP 代碼解釋

使用示例:
FILE:foo.php

<?php

class Foo
{
    static public function getFoo()
    {
        echo "I am foo!\n";
    }
}

FILE:run.php

<?php
ini_set('display_errors', '1');

function __autoload($classname)
{
    $filename = "./". lcfirst($classname) .".php";
    include_once($filename);
}

Foo::getFoo();

結果:

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

結論:
遇到未包含的類,會觸發 __autoload 進行加載,若是全部加載規則中沒有此類,則 Fatal error

Zend 代碼解釋

下面,咱們來看一下 Zend 引擎是如何觸發 __autoload 調用的。
利用 vld 來查看剛纔執行過程當中產生的 opcode,結果以下:
clipboard.png

咱們看到,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,以下圖所示:
clipboard.png

結論

至此,咱們經過 PHP 代碼 Zend 源碼 瞭解了 __autoload 的調用過程。
咱們知道 __autoload 如今已並不推薦使用,
它的缺點也很明顯,不支持多個自動加載函數。

spl_autoload_register

將函數註冊到SPL __autoload函數隊列中。若是該隊列中的函數還沒有激活,則激活它們。若是在你的程序中已經實現了__autoload()函數,它必須顯式註冊到__autoload()隊列中。

PHP 代碼解釋

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

FILE:foo2.class.php

<?php

class Foo2
{
    static public function getFoo2()
    {
        echo "I am foo2!\n";
    }
}

FILE:run.php

<?php
ini_set('display_errors', '1');

$my_autoload1 = function ($classname)
{
    echo "entry my_autoload1 \n";
    $filename = "./". lcfirst($classname) .".php";
    include_once($filename);
};

$my_autoload2 = function ($classname)
{
    echo "entry my_autoload2 \n";
    $filename = "./". lcfirst($classname) .".class.php";
    include_once($filename);
};

spl_autoload_register($my_autoload1);
spl_autoload_register($my_autoload2);

Foo::getFoo();
Foo2::getFoo2();

結果以下:
clipboard.png

咱們看到,調用 getFoo2 時,會先調用第一個註冊的 autoload 方法,若是沒找到對應的類,會產生 warning 並繼續調用後邊註冊的 autoload 方法。
說明了 PHP 內核中爲經過 spl_autoload_register 註冊的 autoload 方法維護了一個隊列,當前文件爲包含調用類,便會觸發此隊列,並依次調用,直到隊列結束 或者 找到對應類。

Zend 源碼解釋

首先,咱們看一下 PHP 文件生成的 opcode
clipboard.png

咱們發現,其方法調用所生成的 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] = '\0';
        }

        if (SPL_G(autoload_functions) && zend_hash_exists(SPL_G(autoload_functions), (char*)lc_name, func_name_len+1)) {
            if (alfi.closure) {
                Z_DELREF_P(zcallable);
            }
            goto skip;
        }

        if (obj_ptr && !(alfi.func_ptr->common.fn_flags & ZEND_ACC_STATIC)) {
            /* add object id to the hash to ensure uniqueness, for more reference look at bug #40091 */
            lc_name = erealloc(lc_name, func_name_len + 2 + sizeof(zend_object_handle));
            memcpy(lc_name + func_name_len, &Z_OBJ_HANDLE_P(obj_ptr), sizeof(zend_object_handle));
            func_name_len += sizeof(zend_object_handle);
            lc_name[func_name_len] = '\0';
            alfi.obj = obj_ptr;
            Z_ADDREF_P(alfi.obj);
        } else {
            alfi.obj = NULL;
        }

        if (!SPL_G(autoload_functions)) {
            ALLOC_HASHTABLE(SPL_G(autoload_functions));
            zend_hash_init(SPL_G(autoload_functions), 1, NULL, (dtor_func_t) autoload_func_info_dtor, 0);
        }

        zend_hash_find(EG(function_table), "spl_autoload", sizeof("spl_autoload"), (void **) &spl_func_ptr);

        if (EG(autoload_func) == spl_func_ptr) { /* registered already, so we insert that first */
            autoload_func_info spl_alfi;

            spl_alfi.func_ptr = spl_func_ptr;
            spl_alfi.obj = NULL;
            spl_alfi.ce = NULL;
            spl_alfi.closure = NULL;
            zend_hash_add(SPL_G(autoload_functions), "spl_autoload", sizeof("spl_autoload"), &spl_alfi, sizeof(autoload_func_info), NULL);
            if (prepend && SPL_G(autoload_functions)->nNumOfElements > 1) {
                /* Move the newly created element to the head of the hashtable */
                HT_MOVE_TAIL_TO_HEAD(SPL_G(autoload_functions));
            }
        }

        if (zend_hash_add(SPL_G(autoload_functions), lc_name, func_name_len+1, &alfi.func_ptr, sizeof(autoload_func_info), NULL) == FAILURE) {
            if (obj_ptr && !(alfi.func_ptr->common.fn_flags & ZEND_ACC_STATIC)) {
                Z_DELREF_P(alfi.obj);
            }                
            if (alfi.closure) {
                Z_DELREF_P(alfi.closure);
            }
        }
        if (prepend && SPL_G(autoload_functions)->nNumOfElements > 1) {
            /* Move the newly created element to the head of the hashtable */
            HT_MOVE_TAIL_TO_HEAD(SPL_G(autoload_functions));
        }
skip:
        efree(lc_name);
    }

    if (SPL_G(autoload_functions)) {
        zend_hash_find(EG(function_table), "spl_autoload_call", sizeof("spl_autoload_call"), (void **) &EG(autoload_func)); /* 注意此處 */
    } else {
        zend_hash_find(EG(function_table), "spl_autoload", sizeof("spl_autoload"), (void **) &EG(autoload_func));
    }
    RETURN_TRUE;
} /* }}} */

經過分析源碼,咱們發現 spl_autoload_register 會把註冊的自動加載函數添加到 autoload_functions 中,最後將 autoload_functions 賦值給 EG(autoload_func) (上方源碼倒數第一個 if 判斷邏輯中)。
而有印象的同窗會發現,EG(autoload_func) 在分析 __autoload 調用源碼時出現過(能夠劃到以前的分析查看),它是執行環境全局結構體中的成員,出現調用大概源碼以下:

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)
{
  ...

    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;

    zend_exception_save(TSRMLS_C);
    retval = zend_call_function(&fcall_info, &fcall_cache TSRMLS_CC);
    zend_exception_restore(TSRMLS_C);

  ...

    return retval;
}

分析到這裏,咱們已經知道了,spl_autoload_register 註冊的函數是如何在 PHP 代碼調用時被觸發的。
感興趣的同窗能夠繼續查看一下 zend_call_function 的源碼,瞭解具體的調用方式。

結論

經過 spl_autoload_register 註冊自動加載函數,會在 Zend 引擎中維護一個 autoload 隊列,便可添加多個 autoload 函數,並在 PHP 調用當前文件未知的類時,觸發 autoload_func 的調用。

同時,細心的同窗也會從 spl_autoload_register 源碼中發現,當註冊時傳入的方法不可調用時, 若是有實現 spl_autoload,也其會被註冊到 autoload 隊列中。

更多使用細節,請參考:


以上分析,若有不適的地方,請多多指教!

相關文章
相關標籤/搜索