摘要:将修改如下直接包含重复包含包含不存在的文件结果结论成功,返回值重复包含,返回,并忽略此次包含总结成功时,都会返回,差别在于包含失败重复包含的处理尝试加载未定义的类。
写在前边
我们将介绍 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_onceinclude_once 行为和 include 语句类似,唯一区别是如果该文件中已经被包含过,则不会再次包含。
将 run.php 修改如下:
结果:
结论:
include_once 重复包含时,会直接返回 1,并忽略此次包含操作,继续执行
requirerequire 和 include 几乎完全一样,但 require 在出错时产生 E_COMPILE_ERROR 级别的错误。(脚本将会中止运行)
将 run.php 修改如下:
结果:
结论:
require 包含成功,同 include 一样,返回值:1
require 包含失败,直接抛出 Fatal error,进程中止
require_oncerequire_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,差别在于 包含失败、重复包含 的处理
__autoloadPHP 代码解释尝试加载未定义的类。此函数将会在 PHP 7.2.0 中弃用。
使用示例:
FILE:foo.phpFILE:run.php
结果:
➜ load git:(master) ✗ php run.php I am foo!结论:
Zend 代码解释
遇到未包含的类,会触发 __autoload 进行加载,如果所有加载规则中没有此类,则 Fatal error。下面,我们来看一下 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 的调用过程。
spl_autoload_register
我们知道 __autoload 现在已并不推荐使用,
它的缺点也很明显,不支持多个自动加载函数。PHP 代码解释将函数注册到SPL __autoload函数队列中。如果该队列中的函数尚未激活,则激活它们。如果在你的程序中已经实现了__autoload()函数,它必须显式注册到__autoload()队列中。
使用示例:
FILE:foo.php
(同上 __autoload)FILE:foo2.class.php
FILE:run.php
结果如下:
我们看到,调用 getFoo2 时,会先调用第一个注册的 autoload 方法,如果没找到对应的类,会产生 warning 并继续调用后边注册的 autoload 方法。
Zend 源码解释
说明了 PHP 内核中为通过 spl_autoload_register 注册的 autoload 方法维护了一个队列,当前文件为包含调用类,便会触发此队列,并依次调用,直到队列结束 或者 找到对应类。首先,我们看一下 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] = " "; } 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] = " "; 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 队列中。
更多使用细节,请参考:
__autoload
spl_autoload_register
SPL 函数
深入理解 PHP 内核
以上分析,如有不适的地方,请多多指教!
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/26157.html
摘要:究竟是什么很多人都是第一次听说的概念,本文主要阐述如何使用的解决方案来实现应用性能的优化。智能的报警机制,在性能瓶颈出现前,修复性能问题,防止性能问题导致用户流失。 APM 究竟是什么? 很多人都是第一次听说 APM 的概念,本文主要阐述如何使用 APM 的解决方案来实现 PHP 应用性能的优化。首先先介绍一下 APM (Application Performance Manageme...
摘要:引导程序包括完成环境检测配置加载异常处理注册服务提供者注册启动服务这六个引导程序。处理请求请求处理发生在内核的方法内。发送响应头部信息发送报文主题终止程序程序终止,完成终止中间件的调用终止中间件以上便是的请求生命周期的始末。 本文首发于个人博客 深度挖掘 Laravel 生命周期,转载请注明出处。 这篇文章我们来聊聊 「Laravel 生命周期」 这个主题。虽然网络上已经有很多关于这个...
摘要:目前来看等语言还难以企及和。作为一个资深的开发者,在技术上给各位程序十点未来的建议,希望对大家有所帮助。开发者应当学习掌握规范,在开发程序时应当尽量遵循规范。程序员除了写后台程序之外,还有很大一部分工作在展现层,和浏览器前端打交道。 PHP 从诞生到现在已经有20多年历史,从Web时代兴起到移动互联网退潮,互联网领域各种编程语言和技术层出不穷, Node.js 、 GO 、 Pytho...
摘要:前言在开始之前,欢迎关注我自己的博客这篇文章是对自动加载功能的一个总结,内容涉及的自动加载功能的命名空间的与标准等内容。要实现第一步,第二步的功能,必须在开发时约定类名与磁盘文件的映射方法,只有这样我们才能根据类名找到它对应的磁盘文件。 前言 在开始之前,欢迎关注我自己的博客:www.leoyang90.cn 这篇文章是对PHP自动加载功能的一个总结,内容涉及PHP的自动加载功能、P...
摘要:从这个例子中,我们可以看出至少要做三件事情根据类名确定类文件名确定类文件所在的磁盘路径将类从磁盘文件中加载到系统中。 深入解析 composer 的自动加载原理 前言 PHP 自5.3的版本之后,已经重焕新生,命名空间、性状(trait)、闭包、接口、PSR 规范、以及 composer 的出现已经让 PHP 变成了一门现代化的脚本语言。PHP 的生态系统也一直在演进,而 compos...
阅读 2830·2021-11-25 09:43
阅读 980·2021-10-11 10:57
阅读 2481·2020-12-03 17:20
阅读 3721·2019-08-30 14:05
阅读 2427·2019-08-29 14:00
阅读 1994·2019-08-29 12:37
阅读 1667·2019-08-26 11:34
阅读 3204·2019-08-26 10:27