前言:
一般来说,自动加载都是和命名空间绑在一起的。我们常说,只要你命名空间和文件目录一致,就可以实现自动加载了,我真的想说,信你个鬼哦,你个糟老头子坏的很。我也不知道在哪里看的这句话,真的以为只要一致就可以自动完成加载。但是当我在项目中集成 Migrations的时候,在项目根目录自己建了一个目录,对应的写着命名空间,但是在控制器use的时候,才发觉并没有导入。在经过仔细研究一番后才发觉框架只对根目录的apps以及核心类库目录进行了自动加载。因此其外的目录并没有引入。于是,我开始研究这个玩意了。写下自己的一些理解与认知。
如果你对命名空间不熟悉,可移步:https://blog.csdn.net/qq_38378384/article/details/79517898
1.自动加载的作用
以前的时候,我们可以将多个类写在同一个php文件中,他们之间可以直接相互使用,慢慢的,随着项目的扩大,我们发觉这种方法并不规范,于是慢慢转变成了一个类就是一个文件,在需要引用其他类时,我们会使用require或者include对其进行引入,这种手动的方式在文件少的时候确实没问题,但项目的类越来越多,要写的require/include也越来越多。phper们觉得这种方式真的太傻了。于是开始想用一种方式可以直接加载,而不用再写那么多的require/include
2.自动加载的实现
2.1 __autoload()
举个例子:
//文件 B.php
<?php
class B{
public function index(){
echo "我是class B中的方法执行结果";
}
}
?>
//文件 A.php
<?php
class A{
public function test(){
$b_object = new B();
$b_object->index();
}
}
function __autoload($classname){
require $classname.'.php';//include 'B.php';
}
$a_object = new A();
$a_oject->test();
?>
命令行输入:#php a.php
输出: “我是class B中的方法执行结果“
这里我们可以看出,我们实现了__autonload()这个函数,这个函数在php找不到类的时候会自动的调用。也就是说当B这个类没有找到的时候,它会执行__autoload()方法,参数一则是该类名,因此,我们只需要在这个方法里面对这个类进行引入即可。
你可以看到,我们就写了个 require $classname.'.php',即引入当前目录的该类名文件,但是一个正常的项目是会有多个目录的,那么里面的实现逻辑可就不能那么简单了,必须得带上目录才行。
那么我们也就需要有一个映射关系,用来找那个类具体所在的目录名。这个映射关系大家可以思考一下如何去实现。
2.2 SPL自动加载
楼上的兄弟是很原始的一种加载方式了。现在我们学一种比较高级的。即:spl的自动加载。
那么我们得先来理解几个有关的函数:
2.2.1 spl_autoload_register() 穿越
将函数注册到SPL __autoload函数队列中。如果该队列中的函数尚未激活,则激活它们。
如果在你的程序中已经实现了__autoload()函数,它必须显式注册到__autoload()队列中。因为 spl_autoload_register()函数会将Zend Engine中的__autoload()函数取代为spl_autoload()或spl_autoload_call()。
如果需要多条 autoload 函数,spl_autoload_register() 满足了此类需求。 它实际上创建了 autoload 函数的队列,按定义时的顺序逐个执行。相比之下, __autoload() 只可以定义一次。
我们可以简单理解成这是用来注册实现__autoload的函数
2.2.2 spl_autoload() 穿越
本函数提供了__autoload()的一个默认实现。如果不使用任何参数调用 spl_autoload_register() 函数,则以后在进行 __autoload() 调用时会自动使用此函数。
2.2.3 spl_autoload_functions() 穿越
获取所有已注册的 __autoload() 函数。
2.2.4 spl_autoload_call() 穿越
可以直接在程序中手动调用此函数来使用所有已注册的__autoload函数装载类或接口
他们之间的逻辑关系又是如何呢?
假设我们自定义了一个autoload($class)。
那么我们需要去注册使用这个东西:spl_autoload_register('autoload'), 那么这个函数就被注册到了 autoload_functions这个hashmap中了,autoload_function是一个全局变量,用来存放注册的autoload方法,
而这些方法又需要使用spl_autoload_call()去主动调用。
当我们使用了spl_autoload_register后,会有一个autoload_func指针,用来指向我们自定义的autoload方法。
因此,如果没有使用spl_autoload_register,则autoload_func为null ,那么autoload的机制可以理解为如下:
检查执行器全局变量函数指针autoload_func是否是NULL;
如果 autoload_func==NULL ,则查找系统是否定义 __autoload() 函数,如果定义了,则执行并返回加载结果。如果没有定义,则报错并退出;
如果 autoload_func 不等于NULL,则直接执行 autoload_func 指向的函数加载类,此时并不检查 __autoload() 函数是否定义。
那么如何简单去实现一个autoload呢?
我们可以简单这么写:
class MCPHP {
//存放要加载的类路径
private static $classPath = [];
/**
* 定义一个无法被子类重写的静态方法
*
**/
final public static function autoLoader($class)
{
//已加载过则直接返回
if (isset(self::$classPath[$class])) {
return;
}
//获取基础目录名
$baseClassPath = \str_replace('\\', DS, $class) . '.php';
//下面可以添加多个目录
$libs = array(
self::getRootPath() . DS . 'application'
);
foreach ($libs as $lib) {
//拼装完整目录
$classpath = $lib . DS . $baseClassPath;
//该目录是文件才执行
if (\is_file($classpath)) {
//将其加入已加载类数组中
self::$classPath[$class] = $classpath;
//进行引入
require_once "{$classpath}";
return;
}
}
}
}
那么,此时命名空间又有啥用呢?即使没有对应目录哪又如何。
其实命名空间的作用之一是在于解决同名类冲突或者函数名冲突等。,而且通过命名空间我们可以取到我们真正想用的类。实际上他是一种规范,我们尽量让命名空间的命名和目录有所联系,可以让代码看起来更加的规范化。但是他和自动加载并没有多大关系,即使我们不使用自动加载,单纯的require/include也是可以使用命名空间的。如果不信的话你可以在框架上故意修改命名空间的名称和目录不一致,即使改了,也不会影响自动加载,