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 交互模式

2.Yii2中代碼自動加載的機制

在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');

3.其中autoload.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()加載文件的順序:

  • 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文件中多瞭如下內容:

'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__);
            }
        }
    }

根據上面的代碼能夠知道:

  • yii2.0框架會根據'@vendor/yiisoft/extensions.php'爲全部第三方擴展設置別名,同時這個別名與擴展代碼所在的目錄相對應。

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


6.擴展:Yii1.1中的自動加載機制

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

2.Yii.php其實是引入了文件 YiiBase.php

3.在YiiBase.php文件中有以下代碼:

spl_autoload_register(array('YiiBase','autoload'));

4.自動加載器YiiBase::autoload()

  • a)先從靜態變量$classMap中找(從代碼中看,這個貌似貌似沒有用到)
  • b)再從靜態變量$_coreClasses中找 (這個寫死在YiiBase文件中了,包含了全部api)
  • c)若是容許 include_path,則直接include
  • d)不容許 include_path,根據self::$_includePaths 逐一查找文件,找到的話就include
  • e)按照類psr-4的方式查找( 與 .)(注意:這裏這裏須要用到一些常見的別名)
/**
     * 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;
    }
相關文章
相關標籤/搜索