ThinkPHP5.1源码分析(二)自动加载机制注册自动加载功能本文默认你有自动加载和命名空间的基础。如果你还没有,请阅读这篇文章。php类的自动加载和命名空间的自动加载机制。php的自动加载是在Loader类中实现的。这个类是在base.php//base.php//加载的Loader类中引入的require__DIR__。'/library/think/Loader.php';//注册自动加载Loader::register();我们的程序在这里执行的是Loader中的静态方法,这也是一个全类register()我们进入Loader。php,按照上面的执行顺序,它的核心是什么?register()方法执行该过程。注册系统自动加载的行数过长。我们一点一点来分析。//注册系统自动加载spl_autoload_register($autoload?:'think\\Loader::autoload',true,true);这是为了注册我们的自动加载功能。$autoload变量是传递的参数。考虑到你可以实现自己的加载类,为了方便扩展,TP允许你实现自己的类加载方法。如果你不知道这个功能,请看文章顶部的链接,里面有详细的解释。Composer自动加载支持$rootPath=self::getRootPath();自我::$composerPath=$rootPath。'小贩'。DIRECTORY_SEPARATOR。“作曲家”。DIRECTORY_SEPARATOR;//Composer自动加载支持'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,方便扩展调用将autoload_static.php中的变量加载到内存中。有一个问题:因为autoload_static.php文件中的类名一直在变,我们得不到固定的类名。(例如,我系统中的类名是ComposerStaticInit5109814b18095308ffe89ba7a1be18df)为了加载requireself::$composerPath中的属性。'autoload_static.php';类名,然后取我们最后加载的类名(即数组中的最后一个)。$declaredClass=get_declared_classes();$composerClass=array_pop($declaredClass);获取我们的类名,调用property_exists($composerClass,$attr)检查类中是否存在指定的属性问题:composer_static的参数代表什么?foreach(['prefixLengthsPsr4','prefixDirsPsr4','fallbackDirsPsr4','prefixesPsr0','fallbackDirsPsr0','classMap','files']as$attr)('fallbackDirsPsr4','prefixesPsr0','fallback'DirsPsr0''classMap','files')是为了什么?classMap(命名空间映射)publicstatic$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__.'/../..',...)命名空间全名和目录的直接映射简单粗暴,也使得这个阵列相当大。PSR4标准顶级命名空间映射数组:publicstatic$prefixLengthsPsr4=array('p'=>array('phpDocumentor\\Reflection\\'=>25,),'S'=>array('Symfony\\Polyfill\\Mbstring\\'=>26,'Symfony\\Component\\Yaml\\'=>23,'Symfony\\Component\\VarDumper\\'=>28,...),...);publicstatic$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数组中得到它的映射目录数组:(注意映射目录可能不止一个)array(0=>__DIR__.'/..'.'/symfony/polyfill-mbstring',)然后我们可以用目录__DIR__替换命名空间Symfony\\Polyfill\\Mbstring\\example的前26个字符。'/..'。'/symfony/polyfill-mbstring,我们得到__DIR__。'/..'。'/symfony/polyfill-mbstring/example.php,首先验证这个文件是否存在于磁盘中,如果不存在,则遍历。如果遍历后没有找到,则加载失败。注:其实作为一个web框架,composer里面的东西ThinkPHP应该不用关心,但是由于TP5自带的原生框架包的设计并没有完全容纳composer,在注册自动加载的时候,它的属性值会被带走,自己使用。(仅供自己理解,如有不同观点,欢迎讨论)注册命名空间定义//注册命名空间定义self::addNamespace(['think'=>__DIR__,'traits'=>dirname(__DIR__).DIRECTORY_SEPARATOR.'特征',]);//加载类库映射文件);}//自动加载扩展目录self::addAutoLoadDir($rootPath.'extend');这背后的代码都是大同小异,都是将需要用到的类映射到Psr4空间。在静态变量中。到时候我们就可以方便的使用命名空间来调用了。//加载类库映射文件);}在TP5代码下执行phpthinkoptimize:autoload会在runtime下生成一个classmap.php文件,文件格式为return['app\\index\\controller\\Index'=>'D:/app/tp5/应用/'。'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()函数大概到这里分析就结束了。在这里我们完成了注册自动加载。要使用自动加载,我们在寄存器Loader::autoload()方法中定义我们的自动加载函数。让我们试试看。在我们的base.php中,我们加载自动加载机制后,我们会加载我们的异常处理//加载Loader类require__DIR__。'/library/think/Loader.php';//注册自动加载Loader::register();//注册错误和异常处理机制Error::register();这种状态下不存在错误,所以一切都会进入我们的自动加载方法并重试。//函数整体内容publicstaticfunctionautoload($class){if(isset(self::$classAlias[$class])){returnclass_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)){返回false;}__include_file($文件);返回真;}}我们截取片段并一点一点地分析它。如果(isset(self::$classAlias[$class])){返回class_alias(self::$classAlias[$class],$class);}这一段是判断我们是否为这个类设置了别名,但是显然我们此时并没有设置。if($file=self::findFile($class)){//Win环境严格区分大小写if(strpos(PHP_OS,'WIN')!==false&&pathinfo($file,PATHINFO_FILENAME)!=pathinfo(真实路径($file),PATHINFO_FILENAME)){returnfalse;}__include_file($文件);返回真;}findFile($class)如果我们之前在runtime文件夹中缓存过classMap,则直接返回。(这就是我们缓存classMap以提高性能的原因)。如果没有缓存,我们可以使用静态数组prefixDirsPsr4和prefixLengthsPsr4来查找文件的目录,速度会慢很多。如果没有找到则返回空,spl_autoload_register会判断该类没有找到并抛出错误。如果找到,则消除linux和windows之间的路径名差异。(linux严格区分大小写,但win不区分大小写)。这里主要关心的是在window环境下,路径名是不区分大小写的,所以我们按照linux的目录规则改写文件路径,然后加入我们的目录文件
