继 生命周期的第二篇,你们尽可放心,不会随便鸽文章的
第一篇中,咱们提到了入口脚本,也说了,里面注册了自动加载的功能php
php 的自动加载是 Loader
类中实现的,这个类在 base.php
中被引入linux
//base .php // 载入Loader类 require __DIR__ . '/library/think/Loader.php'; // 注册自动加载 Loader::register();
咱们程序在这里执行了 Loader 中静态方法 ,同时这也是一个所有的类register()
咱们进入 Loader.php
,按照上面执行顺序看看其核心是什么?web
此方法行数过长,咱们一点一点来分析thinkphp
// 注册系统自动加载 spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true);
这就是注册咱们的自动加载函数,$autoload
这个变量是传的参数,考虑到你能够本身实现本身的加载类,为了方便拓展,TP可让你本身实现本身的类加载方法。数组
若是不了解这个函数的同窗,请看文章最顶部的那个链接,上面有详细讲解。缓存
$rootPath = self::getRootPath(); self::$composerPath = $rootPath . 'vendor' . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR; // Composer自动加载支持 if (is_dir(self::$composerPath)) { if (is_file(self::$composerPath . 'autoload_static.php')) { require self::$composerPath . 'autoload_static.php'; // 获取当前加载的全部类 $declaredClass = get_declared_classes(); $composerClass = array_pop($declaredClass); foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) { if (property_exists($composerClass, $attr)) { self::${$attr} = $composerClass::${$attr}; } } } else { self::registerComposerLoader(self::$composerPath); } }
为了支持 composer 拓展,在自动注册时候,把composer 也顺带一块儿注册了,方便对拓展的调用。app
autoload_static.php中的变量加载进内存中有一个难题:因为autoload_static.php 文件中的类名一直在变化,咱们没法获得固定的类名。(如我系统中 类名为 ComposerStaticInit5109814b18095308ffe89ba7a1be18df
)composer
为了把 require self::$composerPath . 'autoload_static.php';
中 的属性 载入进程序中,在这里咱们换了一种形式框架
首先,获取程序中加载的全部类名,而后取咱们最后一个加载的类名(即数组中的最后一个)。函数
$declaredClass = get_declared_classes(); $composerClass = array_pop($declaredClass);
拿到了咱们的类名,调用 property_exists($composerClass, $attr)
检查类中是否存在指定的属性
foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) 中后面 ('fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files')的做用是什么?
public static $classMap = array ( 'App\\Http\\Controllers\\Auth\\ForgotPasswordController' => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/ForgotPasswordController.php', 'App\\Http\\Controllers\\Auth\\LoginController' => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/LoginController.php', 'App\\Http\\Controllers\\Auth\\RegisterController' => __DIR__ . '/../..' , …… )
直接命名空间全名与目录的映射,简单粗暴,也致使这个数组至关的大。
public static $prefixLengthsPsr4 = array( 'p' => array ( 'phpDocumentor\\Reflection\\' => 25, ), 'S' => array ( 'Symfony\\Polyfill\\Mbstring\\' => 26, 'Symfony\\Component\\Yaml\\' => 23, 'Symfony\\Component\\VarDumper\\' => 28, ... ), ...); public static $prefixDirsPsr4 = array ( 'phpDocumentor\\Reflection\\' => array ( 0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src', 1 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src', 2 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src', ), 'Symfony\\Polyfill\\Mbstring\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', ), 'Symfony\\Component\\Yaml\\' => array ( 0 => __DIR__ . '/..' . '/symfony/yaml', ), ...)
PSR4 标准顶级命名空间映射用了两个数组,第一个是用命名空间第一个字母做为前缀索引,而后是 顶级命名空间,可是最终并非文件路径,而是 顶级命名空间的长度。为何呢?
由于 PSR4 标准是用顶级命名空间目录替换顶级命名空间,因此得到顶级命名空间的长度很重要。
具体说明这些数组的做用:
假如咱们找 Symfony\Polyfill\Mbstring\example
这个命名空间,经过前缀索引和字符串匹配咱们获得了
'Symfony\\Polyfill\\Mbstring\\' => 26,
这条记录,键是顶级命名空间,值是命名空间的长度。拿到顶级命名空间后去 $prefixDirsPsr4
数组 获取它的映射目录数组:(注意映射目录可能不止一条)
<?php 'Symfony\\Polyfill\\Mbstring\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', )
而后咱们就能够将命名空间 Symfony\\Polyfill\\Mbstring\\example
前26个字符替换成目录 __DIR__ . '/..' . '/symfony/polyfill-mbstring
,咱们就获得了__DIR__ . '/..' . '/symfony/polyfill-mbstring/example.php
,先验证磁盘上这个文件是否存在,若是不存在接着遍历。若是遍历后没有找到,则加载失败。
注: 其实做为一个web框架,composer里面的东西,不该该由ThinkPHP关心的,但因为 TP5 本身原生的框架包 的设计没有彻底包容 composer, 所在注册自动加载的时候会拿去其属性值本身来使用(仅限本身理解,若是与您观点不一样欢迎讨论)
// 注册命名空间定义 self::addNamespace([ 'think' => __DIR__, 'traits' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'traits', ]); // 加载类库映射文件 if (is_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')) { self::addClassMap(__include_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')); } // 自动加载extend目录 self::addAutoLoadDir($rootPath . 'extend');
这后面的代码都大同小异,都是把 所须要用到的类,映射到Psr4空间
这个静态变量中。到时候方便咱们使用命名空间进行调用。
// 加载类库映射文件 if (is_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')) { self::addClassMap(__include_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')); }
在 TP5 代码下执行php think optimize:autoload
就会在runtime下生成 classmap.php 文件,文件形式
return [ 'app\\index\\controller\\Index' => 'D:/app/tp5/application/' . 'index/controller/Index.php', 'think\\App' => 'D:/app/tp5/thinkphp/library/' . '/think/App.php', 'think\\Build' => 'D:/app/tp5/thinkphp/library/' . '/think/Build.php', 'think\\Cache' => 'D:/app/tp5/thinkphp/library/' . '/think/Cache.php', 'think\\Collection' => 'D:/app/tp5/thinkphp/library/' . '/think/Collection.php', ... ]
生成类库映射文件,会在runtime
目录下面生成classmap.php
文件,生成的类库映射文件会扫描系统目录和应用目录的类库。在以后碰到了以后直接拿来用,提升系统自动加载的性能。
register()
函数这里就大概分析结束了。 这里咱们就讲完了 注册自动加载。
咱们在 register
中定义了咱们自动加载函数式 Loader::autoload()
方法。 咱们就小试牛刀,在咱们的 base.php
中,咱们加载完 自动加载机制后,就会加载咱们的异常处理
// 载入Loader类 require __DIR__ . '/library/think/Loader.php'; // 注册自动加载 Loader::register(); // 注册错误和异常处理机制 Error::register();
在这时的状态里 Error 不存在,全部会进入咱们的自动加载方法中从新试一下。
//函数总体内容 public static function autoload($class) { if (isset(self::$classAlias[$class])) { return class_alias(self::$classAlias[$class], $class); } if ($file = self::findFile($class)) { // Win环境严格区分大小写 if (strpos(PHP_OS, 'WIN') !== false && pathinfo($file, PATHINFO_FILENAME) != pathinfo(realpath($file), PATHINFO_FILENAME)) { return false; } __include_file($file); return true; } }
咱们截取片断一点一点分析。
if (isset(self::$classAlias[$class])) { return class_alias(self::$classAlias[$class], $class); }
这一段是判断咱们咱们是否对该类设置别名,但明显咱们此时尚未设置。
if ($file = self::findFile($class)) { // Win环境严格区分大小写 if (strpos(PHP_OS, 'WIN') !== false && pathinfo($file, PATHINFO_FILENAME) != pathinfo(realpath($file), PATHINFO_FILENAME)) { return false; } __include_file($file); return true; }
findFile($class)
若是咱们以前缓存了 classMap 在runtime文件夹下,那么他会直接返回。(这也就是为何咱们缓存 classMap 会提高性能的缘由),若是没有缓存就配合咱们以前存储映射关系的静态数组prefixDirsPsr4
,和 prefixLengthsPsr4
来找寻文件的目录,速度会相对慢不少。 若是没有找到那么就返回空, spl_autoload_register
会判断没有找到该类,抛出错误。
若是找到就消除 linux 和 window 对路径名称的差别。(linux 严格区分大小写,而win 没有严格区分)
这里主要是担忧在window环境下,路径名称大小写没分,因此咱们根据linux的目录规则重写了文件路径
以后再加咱们的目录文件