这篇文章我们来看一下laravel使用的自动加载机制,因为我们使用 composer 来管理整个项目,因此这里的自动加载机制其实是 composer 实现的。
首先,从 /public/index.php
我们可以看到,在生成框架实例前,类和函数自动加载相关代码会先被执行。
我们先看启动文件 /bootstrap/autoload.php
。首先它载入了composer的自动加载文件 /vendor/autoload.php
由于整个框架和第三方类库都是用 composer 进行管理的,因此使用它是十分合理的。
接下来它又载入了编译好的类文件 /bootstrap/cache/compiled.php
,用来提高应用的性能
下面我们先看 autoload.php
文件,代码很简单
require_once __DIR__ . '/composer' . '/autoload_real.php';
return ComposerAutoloaderInitf::getLoader();
这是composer自动生成的一个引导文件,它载入了真正的类加载文件,然后调用了引导类的 getLoader
方法。
我们来看 autoload_real.php
,文件定义了ComposerAutoloaderInit
类和composerRequire
方法(为了清晰起见,类和方法后面的标识id省略,部分对分析影响不大的源码也省略)。从上一个文件我们知道它调用了 ComposerAutoloaderInit
类的 getLoader
方法。我们先来分析 getLoader
,代码如下,先简要看注释,了解大致流程,接下来我们再进行分析:
public static function getLoader()
{
// 使用单例模式发挥一个 引导类实例
if (null !== self::$loader) {
return self::$loader;
}
// 注册loadClassLoader加载方法
spl_autoload_register(array('ComposerAutoloaderInit', 'loadClassLoader'), true, true);
// 生成引导类后,又把 loadCLassLoader 引导方法注销
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit', 'loadClassLoader'));
// 如果php版本大于等于5.6
if (PHP_VERSION_ID >= 50600) {
require_once __DIR__ . '/autoload_static.php';
// ComposerStaticInit 类就存在于上述 autoload_static.php 文件中
call_user_func(\Composer\Autoload\ComposerStaticInit::getInitializer($loader));
} else { ... }
// 注册自动加载函数
// 到这里就实现了框架和第三方类库的自动加载
$loader->register(true);
// 载入全局函数文件
if (PHP_VERSION_ID >= 50600) {
$includeFiles = Composer\Autoload\ComposerStaticInit::$files;
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire($fileIdentifier, $file);
}
return $loader;
}
从第十行我们看到了 loadClassLoader
加载函数,其如下
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
它的目的很简单,加载 ClassLoader.php
文件。上面注释说了,加载这个文件的唯一目的就是为了生成 loader
实例,即 \Composer\Autoload\ClassLoader()
。有趣的是,如果不使用自动加载而直接载入这个文件,达到的效果是一样的,至于具体为什么这么写,个人猜测是作者习惯。
现在直接看框架实例 laoder
类,在 ClassLoader
类中,它实现了 PSR-0, PSR-4 和类的自动加载。PSR-0 定义了命名空间和实际文件路径的对应关系等标准,PSR-4 是关于由文件路径自动载入对应类的相关规范,主要是要理清命 全命名空间、命名空间前缀、文件基目录、文件路径 等的关系。强烈建议阅读这个实例,它是psr-4标准的一个实现,对理解整个框架的自动加载机制有很大的帮助(实际上后面的自动加载基本是这个实例的扩展版本)
或者简单来说,这个文件定义了:
1. 类、函数与具体文件的映射方式(通过保存在关联数组来实现)
2. 将这些关系的自动加载函数注册到自动加载队列,实现自动加载。(使用 spl_autoload_register)
3. 定义了命名空间解析成具体路径的方法(psr-4的实现)
4. 定义了具体类解析成具体路径的方法
ok,现在我们只要知道上面的东西就够了,剩下的等遇到了再来看。
回到 autoload_real.php
,以php5.6为例,它又加载进了 autoload_static.php
文件,并调用了 getInitializer
函数。函数如下
public static function getInitializer(ClassLoader $loader)
{
// 复制当前的闭包对象,绑定指定的this和类作用域
// 绑定的对象决定了在匿名函数类中的$this。
// “类作用域”代表一个类型、决定在这个匿名函数中能够调用哪些 私有 和 保护 的方法。
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInit::$prefixesPsr0;
// 主要定义了框架外类,即vendor外的类(大部分是我们的业务类)
$loader->classMap = ComposerStaticInit::$classMap;
}, null, ClassLoader::class);
}
这个函数的作用本身很明显,比较复杂的是它使用了闭包的特性,上面与闭包相关的我们只要知道一点: bind
的第三个参数用来设置类作用域,效果是我们可以在里面修改ClassLoader
类的 proteced, private
属性,也就是上面的 prefixLengthsPsr4
等loader的成员属性。
可以发现,这个函数的作用仅仅是将我们前面提到 命名空间前缀和基路径 等属性进行赋值,简单来说,以后遇到诸如如 ComposerStaticInit::$prefixDirsPsr4
属性(映射数组)中定义的类路径下的类,则可以实现自动加载。查看上述几个类属性,可以发现 laravel框架以及其他第三方类库的库文件以及我们的业务类路径 都已经定义在了上述四个属性中,接下来,执行 $loader->register()
对自动加载函数进行注册,程序执行到这之后,vendor中的类库以及我们自定义的类就实现了自动加载!
后面又载入了全局函数文件(如注释所示)。到这里,整个类的自动加载机制就完成了。
回到/bootstrap/index.php
文件,第一行代码require __DIR__.'/../bootstrap/autoload.php'
执行完毕后,也就实现了 框架类、第三方类和函数以及我们自定义类、函数 的自动加载!
然而悲伤的是,说了这么多,我们还没有踏进 laravel 的大门,不过知道了它现在可以自动加载这么多东西后,以后遇到相关的类文件我们也就不用着急地去探寻它们的require了,composer 已经帮我们搞定了嘛 (=:)