资讯专栏INFORMATION COLUMN

Yii2中的代码自动加载机制

Jaden / 1433人阅读

摘要:中是如何实现代码的自动加载的入口脚本的以下两行代码其中的作用注册为自动加载函数。这个负责引入了一个类中的,随后立即解除注册。注册中的为自动加载函数,并利用配置文件即目录下的文件对这个自动加载函数进行了初始化。

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.类yiiBaseYii的autoload()方法,这个方法是由Yii2框架提供的。


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

require(__DIR__ . "/../vendor/autoload.php");
require(__DIR__ . "/../vendor/yiisoft/yii2/Yii.php");




3.其中autoload.php的作用:

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

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

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(ComposerAutoloadComposerStaticInit06ca19902d5e5679bb4a73b919aadb2a::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 中介绍的所有类,对应文件vendoryiisoftyii2classes.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.vendorcomposerClassLoader.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.在应用的启动过程中,会执行方法yiiaseApplication::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. `yiiaseComponent`), 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 `yiiootstrap` 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;
    }

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/25572.html

相关文章

  • yii2代码学习-BaseYii

    摘要:框架的版本已经发布,版本最低要求是,将会在今年早些时候推出正式版。阅读的代码,能学到很多东西,代码写的很优雅,用到了很多版本的新特性。 yii框架的v-2.0-alpha版本已经发布,PHP版本最低要求是PHP-5.4,将会在今年早些时候推出正式版。阅读yii2的代码,能学到很多东西,代码写的很优雅,用到了很多php-5.4版本的新特性。 BaseYii这个类,在yii2框架中被称作...

    chavesgu 评论0 收藏0
  • Yii修行之路 - Extension 扩展

    摘要:运行来安装指定的扩展。这更便于用户辨别是否是的扩展。当用户运行安装一个扩展时,文件会被自动更新使之包含新扩展的信息。上述代码表明该扩展依赖于包。例如,上述的条目声明将对应于别名。为达到这个目的,你应当在公开发布前做测试。 简述 扩展是专门设计的在 Yii 应用中随时可拿来使用的, 并可重发布的软件包。 基础 例如, yiisoft/yii2-debug 扩展在你的应用的每个页面底部添加...

    bovenson 评论0 收藏0
  • Yii2 完整框架分析(详细)

    摘要:行为是如何注册到组件的呢通过注册行为之后,实际上是添加到了的属性中那么行为中的属性,就添加到了,中进行直接调用行为里面的方法的时候,实际上触发了里面的魔术方法继承链图解 Yii2 框架Trace 准备 了解composer的autoload psr0 psr4 加载机制 了解spl_autoload_register 了解依赖注入的实现原理反射 了解常用魔术方法__set,__get...

    spademan 评论0 收藏0
  • YII2源码分析(1) --- 基本流程分析

    摘要:在分析源码的过程中主要借助了工具。运行应用分析在上面的构造函数执行完后,开始运行应用。发送响应到终端用户入口脚本接收应用主体传来的退出状态并完成请求的处理。 前言 本文主要分析Yii2应用的启动、运行的过程,主要包括以下三部分:入口脚本、启动应用、运行应用。在分析源码的过程中主要借助了Xdebug工具。 入口脚本 文件位置:webindex.php //定义全局变量 defined(...

    ghnor 评论0 收藏0
  • 北哥大话Yii2缓存机制 - 缓存依赖

    摘要:我们都知道,的缓存是支持依赖的,就是我们设置的缓存是否失效除了过期时间还决定于它所依赖的东东是否变化。用好依赖将大大提高我们使用缓存的效果,本节讲解缓存的种依赖方式。现在我们仍然通过一个例子说明,假设我们的一个缓存同时依赖于两个依赖。 我们都知道,yii2的缓存是支持依赖的,就是我们设置的缓存是否失效除了过期时间还决定于它所依赖的东东是否变化。 用好依赖将大大提高我们使用缓存的效果,本...

    klivitamJ 评论0 收藏0

发表评论

0条评论

Jaden

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<