【Yii】自动加载机制和别名

自动加载

什么是类的自动加载,详见PHP官方文档 http://php.net/manual/zh/language.oop5.autoload.php

YII自动加载

Yii的类自动加载,依赖于PHP的 spl_autoload_register() , 注册一个自己的自动加载函数(autoloader),类仅在调用时才会被加载,依赖别名实现了快速定位,这也是YII高性能的一个重要体现。

首先,从入口文件开始分析。

<?php

// comment out the following two lines when deployed to production
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');

require __DIR__ . '/../vendor/autoload.php';//引入composer的自动加载
require __DIR__ . '/../vendor/yiisoft/yii2/Yii.php';//引入YII

$config = require __DIR__ . '/../config/web.php';//引入配置文件

(new yii\web\Application($config))->run();

我们直接来看Yii.php

<?php
require __DIR__ . '/BaseYii.php';

class Yii extends \yii\BaseYii
{
}

spl_autoload_register(['Yii', 'autoload'], true, true);//注册YII的自动加载函数
Yii::$classMap = require __DIR__ . '/classes.php';//导入classMap(一个类的映射表)
Yii::$container = new yii\di\Container();

因为用的是spl_autoload_register(['Yii', 'autoload'], true, true),尽管前面先引入的composer的自动加载,当第三个参数为true时,会将该自动加载放到队首,所以最先使用。

因为Yii.php继承了BaseYii,我们到BaseYii中看autoload方法

public static function autoload($className)
    {
        if (isset(static::$classMap[$className])) {//如果在上面代码引入的classmap中,则直接读取
            $classFile = static::$classMap[$className];
            if ($classFile[0] === '@') {
                $classFile = static::getAlias($classFile);//如果是个别名,就getAlias
            }
        } elseif (strpos($className, '\\') !== false) {//如果类名中含有\则认为是一个正确的类名,将所有的\转换成/并在前面加上@在后面加上.php,作为一个路径别名进行解析,然后执行getAlias方法,得到实际的路径
            $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?");
        }
    }

看到这,就必须得看一下别名了

别名

别名用来表示文件路径和 URL,这样就避免了在代码中硬编码一些绝对路径和 URL。 一个别名必须以 @ 字符开头,以区别于传统的文件路径和 URL。 Yii 预定义了大量可用的别名。例如,别名 @yii 指的是 Yii 框架本身的安装目录,而 @web 表示的是当前运行应用的根 URL。
注意:别名所指向的文件路径或 URL 不一定是真实存在的文件或资源。

\yii\base\Application

public function __construct($config = [])
    {
        Yii::$app = $this;//将application对象赋值给Yii::$app,可以直接用Yii::$app调用application的方法
        static::setInstance($this);

        $this->state = self::STATE_BEGIN;

        $this->preInit($config);//初始化一些配置参数和加载核心组件

        $this->registerErrorHandler($config);//注册错误处理函数
        Component::__construct($config);//注册一些其他的组件和配置
    }

    public function preInit(&$config)
    {
        if (!isset($config['id'])) {
            throw new InvalidConfigException('The "id" configuration for the Application is required.');
        }
        if (isset($config['basePath'])) {
            $this->setBasePath($config['basePath']);//设置@app别名为根目录,这里正是为什么命名空间都是app\开头的原因
            unset($config['basePath']);
        } else {
            throw new InvalidConfigException('The "basePath" configuration for the Application is required.');
        }

        if (isset($config['vendorPath'])) {
            $this->setVendorPath($config['vendorPath']);//设置@vendor,@bower,@npm别名
            unset($config['vendorPath']);
        } else {
            // set "@vendor"
            $this->getVendorPath();
        }

        ...

        // merge core components with custom components
        foreach ($this->coreComponents() as $id => $component) {//注册核心组件
            if (!isset($config['components'][$id])) {
                $config['components'][$id] = $component;
            } elseif (is_array($config['components'][$id]) && !isset($config['components'][$id]['class'])) {
                $config['components'][$id]['class'] = $component['class'];
            }
        }
    }
public function getVendorPath()
{
    // 在未设置vendorPath时,使用默认值
    if ($this->_vendorPath === null) {
        $this->setVendorPath($this->getBasePath() . DIRECTORY_SEPARATOR . 'vendor');
    }

    return $this->_vendorPath;
}

// 这里定义了3个别名
public function setVendorPath($path)
{
    $this->_vendorPath = Yii::getAlias($path);
    Yii::setAlias('@vendor', $this->_vendorPath);
    Yii::setAlias('@bower', $this->_vendorPath . DIRECTORY_SEPARATOR . 'bower');
    Yii::setAlias('@npm', $this->_vendorPath . DIRECTORY_SEPARATOR . 'npm');
}

public function getRuntimePath()
{
    // 在未设置runtimePath时,使用默认值
    if ($this->_runtimePath === null) {
        $this->setRuntimePath($this->getBasePath() . DIRECTORY_SEPARATOR . 'runtime');
    }

    return $this->_runtimePath;
}

// 这里定义了 @runtime 别名
public function setRuntimePath($path)
{
    $this->_runtimePath = Yii::getAlias($path);
    Yii::setAlias('@runtime', $this->_runtimePath);
}

接下来咱们看一下设置别名和获取别名的方法

public static function setAlias($alias, $path)
{
    // 如果拟定义的别名并非以@打头,则在前面加上@
    if (strncmp($alias, '@', 1)) {
        $alias = '@' . $alias;
    }

    // 找到别名的第一段,即@ 到第一个 / 之间的内容,如@foo/bar/qux的@foo
    $pos = strpos($alias, '/');
    $root = $pos === false ? $alias : substr($alias, 0, $pos);

    if ($path !== null) {
        // 去除路径末尾的 \ / 。如果路径本身就是一个别名,直接解析出来
        $path = strncmp($path, '@', 1) ? rtrim($path, '\\/')
            : static::getAlias($path);

        // 检查是否有 $aliases[$root],
        // 看看是否已经定义好了根别名。如果没有,则以$root为键,保存这个别名
        if (!isset(static::$aliases[$root])) {
            if ($pos === false) {
                static::$aliases[$root] = $path;
            } else {
                static::$aliases[$root] = [$alias => $path];
            }
        // 如果 $aliases[$root] 已经存在,则替换成新的路径,或增加新的路径
        } elseif (is_string(static::$aliases[$root])) {//如果是个字符串
            if ($pos === false) {//不含有/,在是根别名
                static::$aliases[$root] = $path;//直接更新
            } else {//否则就加变成数组
                static::$aliases[$root] = [
                    $alias => $path,
                    $root => static::$aliases[$root],
                ];
            }
        } else {
            static::$aliases[$root][$alias] = $path;//添加
            krsort(static::$aliases[$root]);
        }

    // 当传入的 $path 为 null 时,表示要删除这个别名。
    } elseif (isset(static::$aliases[$root])) {
        if (is_array(static::$aliases[$root])) {
            unset(static::$aliases[$root][$alias]);
        } elseif ($pos === false) {
            unset(static::$aliases[$root]);
        }
    }
}

根据上面的方法,可以看到,yii支持给别名再设置一个别名,比如

// 使用一个别名定义另一个别名
Yii::setAlias('@fooqux', '@foo/qux');
public static function getAlias($alias, $throwException = true)
{
    // 一切不以@打头的别名都是无效的,直接返回
    if (strncmp($alias, '@', 1)) {
        return $alias;
    }

    // 先确定根别名 $root
    $pos = strpos($alias, '/');
    $root = $pos === false ? $alias : substr($alias, 0, $pos);

    // 从根别名开始找起,如果根别名没找到,就不存在
    if (isset(static::$aliases[$root])) {
        if (is_string(static::$aliases[$root])) {
            return $pos === false ? static::$aliases[$root] :
                static::$aliases[$root] . substr($alias, $pos);
        } else {
            // 由于写入前使用了 krsort() 所以,较长的别名会被先遍历到。
            foreach (static::$aliases[$root] as $name => $path) {
                if (strpos($alias . '/', $name . '/') === 0) {
                    return $path . substr($alias, strlen($name));
                }
            }
        }
    }

    if ($throwException) {
        throw new InvalidParamException("Invalid path alias: $alias");
    } else {
        return false;
    }
}

别名的解析过程相对简单:

  • 先按根别名找到可能保存别名的列表。
  • 遍历这个列表下的所有别名。由于之前别名是按键值逆排序的,所以优先匹配长别名。
  • 将找到的最长匹配别名替换成其所对应的值。

YII框架的预定义别名如下

array(8) {
  ["@yii"]=>
  array(6) {
    ["@yii/swiftmailer"]=>
    string(63) "basic/vendor/yiisoft/yii2-swiftmailer"
    ["@yii/gii"]=>
    string(55) "basic/vendor/yiisoft/yii2-gii"
    ["@yii/faker"]=>
    string(57) "basic/vendor/yiisoft/yii2-faker"
    ["@yii/debug"]=>
    string(57) "basic/vendor/yiisoft/yii2-debug"
    ["@yii/bootstrap"]=>
    string(61) "basic/vendor/yiisoft/yii2-bootstrap"
    ["@yii"]=>
    string(51) "basic/vendor/yiisoft/yii2"
  }
  ["@app"]=>
  string(31) "basic"
  ["@vendor"]=>
  string(38) "basic/vendor"
  ["@bower"]=>
  string(50) "basic/vendor/bower-asset"
  ["@npm"]=>
  string(48) "basic/vendor/npm-asset"
  ["@runtime"]=>
  string(39) "basic/runtime"
  ["@webroot"]=>
  string(1) "."
  ["@web"]=>
  string(1) "."
}

结束语

Yii的自动加载,就是结合类的映射表+命名空间别名的形式进行快速定位和载入的,本文只是简单的罗列了一下代码,关于更深层次的东西,可以自己根据编辑器代码跟踪功能一步步查看。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值