Home  >  Article  >  Backend Development  >  Code automatic loading mechanism in Yii2

Code automatic loading mechanism in Yii2

小云云
小云云Original
2017-12-09 16:48:161988browse

本文我们和大家分享Yii2中的代码自动加载机制知识,希望能帮助到大家。

1.基本知识

  • Include与require 的作用:
     当一个文件被包含时,其中所包含的代码继承了 include 所在行的变量范围。从该处开始,调用文件在该行处可用的任何变量在被调用的文件中也都可用。不过所有在包含文件中定义的函数和类都具有全局作用域。

  • Include与require的区别:
     未找到文件则 include 结构会发出一条警告;require 会发出一个致命错误。


  • 如何实现类的自动加载
    bool spl_autoload_register ([ callable $autoload_function [, bool $throw = true [, bool $prepend = false ]]] )
    (__autoload() 已被弃用)
    可以注册任意数量的自动加载器
    注意:自动加载不可用于 PHP 的 CLI 交互模式


  • 自动加载规范
      1.PSR-0:https://github.com/PizzaLiu/P...
      2.PSR-4:https://github.com/PizzaLiu/P...
      3.PEAR:全是以"_"作为分隔

2.Yii2中代码自动加载的机制

在Yii2.0的运行过程中主要由以下两个方法来实现代码的自动加载:
1.path_to_your_project/vendor/composer/ClassLoader.php中的ClassLoader::loadClass()方法,这个方法是由composer提供的。

2.类\yii\BaseYii的autoload()方法,这个方法是由Yii2框架提供的。


Yii2中是如何实现代码的自动加载的?
入口脚本的以下两行代码:

<span style="font-size: 14px;">require(__DIR__ . '/../vendor/autoload.php');<br>require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php');<br></span>

3.其中autoload.php的作用:

1.注册ComposerAutoloaderInit06ca19902d5e5679bb4a73b919aadb2a::loadClassLoader($class)为自动加载函数。这个loader负责引入了一个类:ClassLoader.php中的\Composer\Autoload\ClassLoader(),随后立即解除注册。

2.注册vendor/composer/ClassLoader.php中的ClassLoader::loadClass($class)为自动加载函数,并利用配置文件(即vendor/composer目录下的autoload_*.php文件)对这个自动加载函数进行了初始化。这个函数实现了PSR-0,PSR-4,classmap等方式来自动加载。

3.Require “vendor/composer/autoload_static.php”中的$files(作为全局函数使用)

4.将2中的loader返回到入口脚本

注意:
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的目录或文件)

<span style="font-size: 14px;">return array(<br>            'HTMLPurifier' => array($vendorDir . '/ezyang/htmlpurifier/library'),<br>            'Diff' => array($vendorDir . '/phpspec/php-diff/lib'),<br>        );<br></span>

如何处理上面的配置数组?   答:将数据配置到数组prefixesPsr0中

<span style="font-size: 14px;">$map = require __DIR__ . '/autoload_namespaces.php';<br>foreach ($map as $namespace => $path) {<br>    $loader->set($namespace, $path);<br>}<br><br><br>ClassLoader::set()<br>public function set($prefix, $paths)<br>{<br>    if (!$prefix) { //若为空串,则设置一个目录作为任何命名空间的备用目录(相当于默认目录)<br>        $this->fallbackDirsPsr0 = (array) $paths;<br>    } else {<br>        //prefixesPsr0数组,参考autoload_static.php文件<br>        $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;<br>    }<br>}<br></span>

3.autoload_psr4.php:
a)包含的命名空间目录:vendor/yiisoft下满足psr-4的目录(包括yii命名空间,即yii api 中包含的类)

如何处理上面的配置数组? 答 : 将数据配置到数组prefixLengthsPsr4,prefixDirsPsr4中

<span style="font-size: 14px;">$map = require __DIR__ . '/autoload_namespaces.php';<br>foreach ($map as $namespace => $path) {<br>    $loader->setPsr4($namespace, $path);<br>}<br><br><br>public function setPsr4($prefix, $paths)<br>{<br>    if (!$prefix) {//若为空串,则设置一个目录作为任何命名空间的备用目录(相当于默认目录)<br>        $this->fallbackDirsPsr4 = (array) $paths;<br>    } else {<br>        $length = strlen($prefix);<br>        if ('\\' !== $prefix[$length - 1]) {<br>            throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");<br>        }<br>        $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;<br>        $this->prefixDirsPsr4[$prefix] = (array) $paths;<br>    }<br>}<br></span>

4.autoload_classmap.php
a)命令"composer dump-autoload -o"会生成这么一个文件,通过classmap方式,可以提高自动加载的效率        
(相比使用PSR-0或PSR-4自动加载,可以减少计算量和IO,后面会详细分析)

如何处理上面的配置数组? 答:将数据配置到数组classMap中

<span style="font-size: 14px;">$classMap = require __DIR__ . '/autoload_classmap.php';<br>if ($classMap) {<br>    $loader->addClassMap($classMap);<br>}<br><br><br><br>public function addClassMap(array $classMap)<br>{<br>    if ($this->classMap) {<br>        $this->classMap = array_merge($this->classMap, $classMap);<br>    } else {<br>        $this->classMap = $classMap;<br>    }<br>}<br></span>

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个文件。

<span style="font-size: 14px;">require_once __DIR__ . '/autoload_static.php';<br>call_user_func(\Composer\Autoload\ComposerStaticInit06ca19902d5e5679bb4a73b919aadb2a::getInitializer($loader));<br>                                <br>                                <br>//上面的getInitializer()具体做什么?<br>//其实就是直接把已经生成好的prefixLengthsPsr4,prefixDirsPsr4,prefixesPsr0,classMap一一赋值给loader,而不是像上面提到的那样一个一个配置<br>public static function getInitializer(ClassLoader $loader)<br>{<br>    return \Closure::bind(function () use ($loader) {<br>        $loader->prefixLengthsPsr4 = ComposerStaticInit06ca19902d5e5679bb4a73b919aadb2a::$prefixLengthsPsr4;<br>        $loader->prefixDirsPsr4 = ComposerStaticInit06ca19902d5e5679bb4a73b919aadb2a::$prefixDirsPsr4;<br>        $loader->prefixesPsr0 = ComposerStaticInit06ca19902d5e5679bb4a73b919aadb2a::$prefixesPsr0;<br>        $loader->classMap = ComposerStaticInit06ca19902d5e5679bb4a73b919aadb2a::$classMap;<br>    }, null, ClassLoader::class);<br>}<br></span>

上面关于Closure::bind()的使用参考http://www.cnblogs.com/iforev...

7.总结:(对应关系)

<span style="font-size: 14px;">prefixLengthsPsr4  <=>  autoload_psr4.php<br>prefixDirsPsr4  <=>  autoload_psr4.php<br>prefixesPsr0  <=>  autoload_namespaces.php(lib或src目录,使用psr0)<br>classMap  <=>  autoload_classmap<br></span>

使用ComposerAutoloadClassLoader::loadClass()加载文件的顺序:

  • 1.先从classMap中找(时间复杂度O(1))

  • 2.查看是否文件之前已经查找过,证实不存在($missingClasses)

  • 3.如果有使用apc缓存的话从缓存中取

  • 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

  • 5.如果有使用HHVM的话,找后缀为”.hh”的文件,回到4下的具体查找(即a,b,c..)

  • 6.如果有使用apc缓存的话,将找到的文件的绝对路径存储到apc缓存中

注意:
1.在ClassLoader::findFileWithExtension($class, $ext)中实现了PSR-0和PSR-4的自动加载,其时间复杂度均为O(n2),相比于classmap的方式而言(时间复杂度为O(1))是低效的,因此在生产环境中可以采用composer命令"composer dump-autoload -o"进行优化。


4.其中Yii.php 的作用:

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提供的。


5.YII2.0自动加载第三方扩展

准备:通过composer require安装yii上的第三方扩展。
例如
$ php composer.phar require kartik-v/yii2-markdown "dev-master"

1.安装完成之后可以发现vendor/yiisoft/extensions.php文件中多了以下内容:

<span style="font-size: 14px;">'kartik-v/yii2-markdown' => <br>  array (<br>    'name' => 'kartik-v/yii2-markdown',<br>    'version' => '9999999-dev',<br>    'alias' => <br>    array (<br>      '@kartik/markdown' => $vendorDir . '/kartik-v/yii2-markdown',<br>    ),<br></span>

2.在应用的启动过程中,会执行方法yii\base\Application::bootstrap()

<span style="font-size: 14px;">/**<br>     * Initializes extensions and executes bootstrap components.<br>     * This method is called by [[init()]] after the application has been fully configured.<br>     * If you override this method, make sure you also call the parent implementation.<br>     */<br>    protected function bootstrap()<br>    {<br>        if ($this->extensions === null) {<br>            $file = Yii::getAlias('@vendor/yiisoft/extensions.php');<br>            $this->extensions = is_file($file) ? include($file) : [];<br>        }<br>        foreach ($this->extensions as $extension) {<br>            if (!empty($extension['alias'])) {<br>                foreach ($extension['alias'] as $name => $path) {<br>                    Yii::setAlias($name, $path);<br>                }<br>            }<br>            if (isset($extension['bootstrap'])) {<br>                $component = Yii::createObject($extension['bootstrap']);<br>                if ($component instanceof BootstrapInterface) {<br>                    Yii::trace('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);<br>                    $component->bootstrap($this);<br>                } else {<br>                    Yii::trace('Bootstrap with ' . get_class($component), __METHOD__);<br>                }<br>            }<br>        }<br><br>        foreach ($this->bootstrap as $class) {<br>            $component = null;<br>            if (is_string($class)) {<br>                if ($this->has($class)) {<br>                    $component = $this->get($class);<br>                } elseif ($this->hasModule($class)) {<br>                    $component = $this->getModule($class);<br>                } elseif (strpos($class, '\\') === false) {<br>                    throw new InvalidConfigException("Unknown bootstrapping component ID: $class");<br>                }<br>            }<br>            if (!isset($component)) {<br>                $component = Yii::createObject($class);<br>            }<br><br>            if ($component instanceof BootstrapInterface) {<br>                Yii::trace('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);<br>                $component->bootstrap($this);<br>            } else {<br>                Yii::trace('Bootstrap with ' . get_class($component), __METHOD__);<br>            }<br>        }<br>    }<br></span>

根据上面的代码可以知道:

  • yii2.0框架会根据'@vendor/yiisoft/extensions.php'为所有第三方扩展设置别名,同时这个别名与扩展代码所在的目录相对应。

3.在需要的时候使用Yii::autoload()加载

<span style="font-size: 14px;">/**<br>     * Class autoload loader.<br>     * This method is invoked automatically when PHP sees an unknown class.<br>     * The method will attempt to include the class file according to the following procedure:<br>     *<br>     * 1. Search in [[classMap]];<br>     * 2. If the class is namespaced (e.g. `yii\base\Component`), it will attempt<br>     *    to include the file associated with the corresponding path alias<br>     *    (e.g. `@yii/base/Component.php`);<br>     *<br>     * This autoloader allows loading classes that follow the [PSR-4 standard](http://www.php-fig.org/psr/psr-4/)<br>     * and have its top-level namespace or sub-namespaces defined as path aliases.<br>     *<br>     * Example: When aliases `@yii` and `@yii/bootstrap` are defined, classes in the `yii\bootstrap` namespace<br>     * will be loaded using the `@yii/bootstrap` alias which points to the directory where bootstrap extension<br>     * files are installed and all classes from other `yii` namespaces will be loaded from the yii framework directory.<br>     *<br>     * Also the [guide section on autoloading](guide:concept-autoloading).<br>     *<br>     * @param string $className the fully qualified class name without a leading backslash "\"<br>     * @throws UnknownClassException if the class does not exist in the class file<br>     */<br>    public static function autoload($className)<br>    {<br>        if (isset(static::$classMap[$className])) {<br>            $classFile = static::$classMap[$className];<br>            if ($classFile[0] === '@') {<br>                $classFile = static::getAlias($classFile);<br>            }<br>        } elseif (strpos($className, '\\') !== false) {<br>            $classFile = static::getAlias('@' . str_replace('\\', '/', $className) . '.php', false);<br>            if ($classFile === false || !is_file($classFile)) {<br>                return;<br>            }<br>        } else {<br>            return;<br>        }<br><br>        include($classFile);<br><br>        if (YII_DEBUG && !class_exists($className, false) && !interface_exists($className, false) && !trait_exists($className, false)) {<br>            throw new UnknownClassException("Unable to find '$className' in file: $classFile. Namespace missing?");<br>        }<br>    }<br></span>

4.总结:可以认为说第三方扩展的自动加载其实就是使用了别名解析和PSR-4


6.扩展:Yii1.1中的自动加载机制

1.在index.php中加载 yii.php

2.Yii.php实际上是引入了文件 YiiBase.php

3.在YiiBase.php文件中有如下代码:

<span style="font-size: 14px;">spl_autoload_register(array('YiiBase','autoload'));<br></span>

4.自动加载器YiiBase::autoload()

  • a)先从静态变量$classMap中找(从代码中看,这个貌似貌似没有用到)

  • b)再从静态变量$_coreClasses中找 (这个写死在YiiBase文件中了,包含了所有api)

  • c)如果允许 include_path,则直接include

  • d)不允许 include_path,根据self::$_includePaths 逐一查找文件,找到的话就include

  • e)按照类psr-4的方式查找( 与 .)(注意:这里这里需要用到一些常见的别名)

<span style="font-size: 14px;">/**<br>     * Class autoload loader.<br>     * This method is provided to be invoked within an __autoload() magic method.<br>     * @param string $className class name<br>     * @return boolean whether the class has been loaded successfully<br>     */<br>    public static function autoload($className)<br>    {<br>        // use include so that the error PHP file may appear<br>        if(isset(self::$classMap[$className]))<br>            include(self::$classMap[$className]);<br>        elseif(isset(self::$_coreClasses[$className]))//这个数组是写死在YiiBase中的,包含所有yii api<br>            include(YII_PATH.self::$_coreClasses[$className]);<br>        else<br>        {<br>            // include class file relying on include_path<br>            if(strpos($className,'\\')===false)  // class without namespace<br>            {<br>                if(self::$enableIncludePath===false)<br>                {<br>                    foreach(self::$_includePaths as $path)<br>                    {<br>                        $classFile=$path.DIRECTORY_SEPARATOR.$className.'.php';<br>                        if(is_file($classFile))<br>                        {<br>                            include($classFile);<br>                            if(YII_DEBUG && basename(realpath($classFile))!==$className.'.php')<br>                                throw new CException(Yii::t('yii','Class name "{class}" does not match class file "{file}".', array(<br>                                    '{class}'=>$className,<br>                                    '{file}'=>$classFile,<br>                                )));<br>                            break;<br>                        }<br>                    }<br>                }<br>                else<br>                    include($className.'.php');<br>            }<br>            else  // class name with namespace in PHP 5.3<br>            {<br>                $namespace=str_replace('\\','.',ltrim($className,'\\'));<br>                if(($path=self::getPathOfAlias($namespace))!==false)<br>                    include($path.'.php');<br>                else<br>                    return false;<br>            }<br>            return class_exists($className,false) || interface_exists($className,false);<br>        }<br>        return true;<br>    }<br></span>

 相关推荐:

Yii2中关于组件的注册以及创建的方法详解

几个php中类的自动加载的方法的实例详解

深入学习php自动加载机制                 

The above is the detailed content of Code automatic loading mechanism in Yii2. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn