在Yii2.0的运行过程当中主要由如下两个方法来实现代码的自动加载:
1.path_to_your_project/vendor/composer/ClassLoader.php中的ClassLoader::loadClass()方法,这个方法是由composer提供的。 php
2.类\yii\BaseYii的autoload()方法,这个方法是由Yii2框架提供的。html
Yii2中是如何实现代码的自动加载的?
入口脚本的如下两行代码:laravel
require(__DIR__ . '/../vendor/autoload.php'); require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php');
1.注册ComposerAutoloaderInit06ca19902d5e5679bb4a73b919aadb2a::loadClassLoader($class)为自动加载函数。这个loader负责引入了一个类:ClassLoader.php中的\Composer\Autoload\ClassLoader(),随后当即解除注册。 git
2.注册vendor/composer/ClassLoader.php中的ClassLoader::loadClass($class)为自动加载函数,并利用配置文件(即vendor/composer目录下的autoload_*.php文件)对这个自动加载函数进行了初始化。这个函数实现了PSR-0,PSR-4,classmap等方式来自动加载。 github
3.Require “vendor/composer/autoload_static.php”中的$files(做为全局函数使用) bootstrap
4.将2中的loader返回到入口脚本api
注意:
1.正如前面所提到的ClassLoader::loadClass($class)这个方法是由composer提供的,而配置文件(即vendor/composer目录下的autoload_*.php文件)则是在执行composer命令update/install的时候生成的。更多关于composer自动加载的内容参考composer自动加载,深刻学习composer自动加载机制数组
对vendor/composer/ClassLoader.php中的ClassLoader::loadClass($class)详细分析:
1.该loader有4个配置文件 : autoload_namespaces.php,autoload_psr4.php,autoload_classmap.php,autoload_files.php(这4个文件都是由composer生成的),还有一个比较特殊的文件autoload_static(也是由composer生成的,主要是为了提升效率,至关于缓存) 缓存
2.autoload_namespaces.php:(对应的是一些符合PSR-0的目录或文件)yii2
return array( 'HTMLPurifier' => array($vendorDir . '/ezyang/htmlpurifier/library'), 'Diff' => array($vendorDir . '/phpspec/php-diff/lib'), );
如何处理上面的配置数组? 答:将数据配置到数组prefixesPsr0中
$map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { $loader->set($namespace, $path); } ClassLoader::set() public function set($prefix, $paths) { if (!$prefix) { //若为空串,则设置一个目录做为任何命名空间的备用目录(至关于默认目录) $this->fallbackDirsPsr0 = (array) $paths; } else { //prefixesPsr0数组,参考autoload_static.php文件 $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; } }
3.autoload_psr4.php:
a)包含的命名空间目录:vendor/yiisoft下知足psr-4的目录(包括yii命名空间,即yii api 中包含的类)
如何处理上面的配置数组? 答 : 将数据配置到数组prefixLengthsPsr4,prefixDirsPsr4中
$map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { $loader->setPsr4($namespace, $path); } public function setPsr4($prefix, $paths) { if (!$prefix) {//若为空串,则设置一个目录做为任何命名空间的备用目录(至关于默认目录) $this->fallbackDirsPsr4 = (array) $paths; } else { $length = strlen($prefix); if ('\\' !== $prefix[$length - 1]) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = (array) $paths; } }
4.autoload_classmap.php
a)命令"composer dump-autoload -o"会生成这么一个文件,经过classmap方式,能够提升自动加载的效率
(相比使用PSR-0或PSR-4自动加载,能够减小计算量和IO,后面会详细分析)
如何处理上面的配置数组? 答:将数据配置到数组classMap中
$classMap = require __DIR__ . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); } public function addClassMap(array $classMap) { if ($this->classMap) { $this->classMap = array_merge($this->classMap, $classMap); } else { $this->classMap = $classMap; } }
5.autoload_files.php
a)主要包括了须要当即require的文件( 一般是库文件,也能够是自定义的,引入做为全局函数使用)
如何处理:直接require
6.autoload_static.php
当知足如下条件:
1.PHP_VERSION_ID >= 50600
2.&& !defined('HHVM_VERSION')
3.&& (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
则直接使用autoload_static文件而不采用上面的4个文件。
require_once __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInit06ca19902d5e5679bb4a73b919aadb2a::getInitializer($loader)); //上面的getInitializer()具体作什么? //其实就是直接把已经生成好的prefixLengthsPsr4,prefixDirsPsr4,prefixesPsr0,classMap一一赋值给loader,而不是像上面提到的那样一个一个配置 public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInit06ca19902d5e5679bb4a73b919aadb2a::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInit06ca19902d5e5679bb4a73b919aadb2a::$prefixDirsPsr4; $loader->prefixesPsr0 = ComposerStaticInit06ca19902d5e5679bb4a73b919aadb2a::$prefixesPsr0; $loader->classMap = ComposerStaticInit06ca19902d5e5679bb4a73b919aadb2a::$classMap; }, null, ClassLoader::class); }
上面关于Closure::bind()的使用参考http://www.cnblogs.com/iforev...
7.总结:(对应关系)
prefixLengthsPsr4 <=> autoload_psr4.php prefixDirsPsr4 <=> autoload_psr4.php prefixesPsr0 <=> autoload_namespaces.php(lib或src目录,使用psr0) classMap <=> autoload_classmap
使用ComposerAutoloadClassLoader::loadClass()加载文件的顺序:
4.查找文件名后缀为”.php”的文件
- a)PSR-4 lookup:格式化类名,经过prefixLengthsPsr4,prefixDirsPsr4找到文件的绝对路径 - b)PSR-4 fallback:根据$fallbackDirsPsr4查找根命名空间下的目录 - c)PSR-0 lookup:分纯pear格式,pear+命名空间格式,根据prefixesPsr0找到文件的绝对路径 - d)PSR-0 lookup:根据fallbackDirsPsr0查找根命名空间下的目录 - e)PSR-0 include:若是容许使用include path方式的话,使用stream_resolve_include_path()返回绝对路径 - f)找不到,返回false
注意:
1.在ClassLoader::findFileWithExtension($class, $ext)中实现了PSR-0和PSR-4的自动加载,其时间复杂度均为O(n2),相比于classmap的方式而言(时间复杂度为O(1))是低效的,所以在生产环境中能够采用composer命令"composer dump-autoload -o"进行优化。
1.定义类Yii(须要手动引入其父类的文件,而不是靠自动加载)
2.注册Yii::autoload()为自动加载函数
3.赋值Yii::$classMap (其值即yii2 api 中介绍的全部类,对应文件vendor\yiisoft\yii2\classes.php)
4.生成依赖注入容器:Yii::$container = new yiidiContainer();
相对于ComposerAutoloadClassLoader::loadClass(),Yii.php所作的就简单明了许多了,若是所需加载的类在Yii::$classMap中有定义则直接经过它加载,没有的话就解析别名,而后加载。若是解析别名后依然找不到相应的文件路径,则使用composer提供的自动加载函数来加载(即ClassLoader::loadClass($class))
Yii::autoload()主要能引入什么类?
1.Yii::$classMap中定义的yii2核心类
2.引入咱们的应用中本身建立的类(依靠命名空间和别名,遵循PSR-4)
最终注册了的自动加载方法以及顺序:
1.Yii::autoload()
2.vendor\composer\ClassLoader.php中的ClassLoader::loadClass($class)
注意 :
1.经过上面的两个方法均可以引入Yii2.0框架自身的全部类,在顺序上会优先使用Yii::autoload()(主要是利用了Yii::classMap)
2.Yii::autoload()是由Yii框架提供的,而ClassLoader->loadClass($class)是由composer提供的。
准备:经过composer require安装yii上的第三方扩展。
例如
$ php composer.phar require kartik-v/yii2-markdown "dev-master"
1.安装完成以后能够发现vendor/yiisoft/extensions.php文件中多了如下内容:
'kartik-v/yii2-markdown' => array ( 'name' => 'kartik-v/yii2-markdown', 'version' => '9999999-dev', 'alias' => array ( '@kartik/markdown' => $vendorDir . '/kartik-v/yii2-markdown', ),
2.在应用的启动过程当中,会执行方法yii\base\Application::bootstrap()
/** * Initializes extensions and executes bootstrap components. * This method is called by [[init()]] after the application has been fully configured. * If you override this method, make sure you also call the parent implementation. */ protected function bootstrap() { if ($this->extensions === null) { $file = Yii::getAlias('@vendor/yiisoft/extensions.php'); $this->extensions = is_file($file) ? include($file) : []; } foreach ($this->extensions as $extension) { if (!empty($extension['alias'])) { foreach ($extension['alias'] as $name => $path) { Yii::setAlias($name, $path); } } if (isset($extension['bootstrap'])) { $component = Yii::createObject($extension['bootstrap']); if ($component instanceof BootstrapInterface) { Yii::trace('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__); $component->bootstrap($this); } else { Yii::trace('Bootstrap with ' . get_class($component), __METHOD__); } } } foreach ($this->bootstrap as $class) { $component = null; if (is_string($class)) { if ($this->has($class)) { $component = $this->get($class); } elseif ($this->hasModule($class)) { $component = $this->getModule($class); } elseif (strpos($class, '\\') === false) { throw new InvalidConfigException("Unknown bootstrapping component ID: $class"); } } if (!isset($component)) { $component = Yii::createObject($class); } if ($component instanceof BootstrapInterface) { Yii::trace('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__); $component->bootstrap($this); } else { Yii::trace('Bootstrap with ' . get_class($component), __METHOD__); } } }
根据上面的代码能够知道:
3.在须要的时候使用Yii::autoload()加载
/** * Class autoload loader. * This method is invoked automatically when PHP sees an unknown class. * The method will attempt to include the class file according to the following procedure: * * 1. Search in [[classMap]]; * 2. If the class is namespaced (e.g. `yii\base\Component`), it will attempt * to include the file associated with the corresponding path alias * (e.g. `@yii/base/Component.php`); * * This autoloader allows loading classes that follow the [PSR-4 standard](http://www.php-fig.org/psr/psr-4/) * and have its top-level namespace or sub-namespaces defined as path aliases. * * Example: When aliases `@yii` and `@yii/bootstrap` are defined, classes in the `yii\bootstrap` namespace * will be loaded using the `@yii/bootstrap` alias which points to the directory where bootstrap extension * files are installed and all classes from other `yii` namespaces will be loaded from the yii framework directory. * * Also the [guide section on autoloading](guide:concept-autoloading). * * @param string $className the fully qualified class name without a leading backslash "\" * @throws UnknownClassException if the class does not exist in the class file */ public static function autoload($className) { if (isset(static::$classMap[$className])) { $classFile = static::$classMap[$className]; if ($classFile[0] === '@') { $classFile = static::getAlias($classFile); } } elseif (strpos($className, '\\') !== false) { $classFile = static::getAlias('@' . str_replace('\\', '/', $className) . '.php', false); if ($classFile === false || !is_file($classFile)) { return; } } else { return; } include($classFile); if (YII_DEBUG && !class_exists($className, false) && !interface_exists($className, false) && !trait_exists($className, false)) { throw new UnknownClassException("Unable to find '$className' in file: $classFile. Namespace missing?"); } }
4.总结:能够认为说第三方扩展的自动加载其实就是使用了别名解析和PSR-4
1.在index.php中加载 yii.php
2.Yii.php其实是引入了文件 YiiBase.php
3.在YiiBase.php文件中有以下代码:
spl_autoload_register(array('YiiBase','autoload'));
4.自动加载器YiiBase::autoload()
/** * Class autoload loader. * This method is provided to be invoked within an __autoload() magic method. * @param string $className class name * @return boolean whether the class has been loaded successfully */ public static function autoload($className) { // use include so that the error PHP file may appear if(isset(self::$classMap[$className])) include(self::$classMap[$className]); elseif(isset(self::$_coreClasses[$className]))//这个数组是写死在YiiBase中的,包含全部yii api include(YII_PATH.self::$_coreClasses[$className]); else { // include class file relying on include_path if(strpos($className,'\\')===false) // class without namespace { if(self::$enableIncludePath===false) { foreach(self::$_includePaths as $path) { $classFile=$path.DIRECTORY_SEPARATOR.$className.'.php'; if(is_file($classFile)) { include($classFile); if(YII_DEBUG && basename(realpath($classFile))!==$className.'.php') throw new CException(Yii::t('yii','Class name "{class}" does not match class file "{file}".', array( '{class}'=>$className, '{file}'=>$classFile, ))); break; } } } else include($className.'.php'); } else // class name with namespace in PHP 5.3 { $namespace=str_replace('\\','.',ltrim($className,'\\')); if(($path=self::getPathOfAlias($namespace))!==false) include($path.'.php'); else return false; } return class_exists($className,false) || interface_exists($className,false); } return true; }