写在前面,写这些随笔是记录下自己看Yii2源码的过程,可能会有些流水账,大部分解析放在注释里说明,由于个人水平有限,有不正确的地方还望斧正。
以下源码版本基于Yii2的2.0.34版本,模板用的基础版。
web入口文件Index.php
// 定义全局的常量,YII_DEBUG标识是够开启debug模式,YII_ENV标识出当前运行环境,默认env(开发), 上线后改成prod来表示正式环境。 defined('YII_DEBUG') or define('YII_DEBUG', true); defined('YII_ENV') or define('YII_ENV', 'dev'); // 加载composer vendor的autoload文件 require(__DIR__ . '/../vendor/autoload.php'); // 加载Yii框架 require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php');
入口文件很简单,做了一些初始化工作,具体看注释。
接下来看加载的Yii文件代码:
php /** * Yii bootstrap file. * * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ require(__DIR__ . '/BaseYii.php'); /** * Yii is a helper class serving common framework functionalities. * * It extends from [[\yii\BaseYii]] which provides the actual implementation. * By writing your own Yii class, you can customize some functionalities of [[\yii\BaseYii]]. * * @author Qiang Xue* @since 2.0 */ class Yii extends \yii\BaseYii { } // 调用Yii::autoload来注册autoload, 而且是放到autoload队列之首。 spl_autoload_register(['Yii', 'autoload'], true, true); // 包含class映射文件 Yii::$classMap = require(__DIR__ . '/classes.php'); // 初始化DI容器 Yii::$container = new yii\di\Container();
PS:这边涉及到php的自动加载概念(https://www.php.net/manual/zh/function.spl-autoload-register)和设计模式-依赖注入(https://www.kancloud.cn/kancloud/yii-in-depth/50793)。
这个文件主要进行一些Yii源码的初始化操作,这里的class Yii
只是继承了BaseYii
,没有写任何代码,所以yii2的源码都是在BaseYii里面的,这里留空是为了给使用者自定义的。
注册自动加载
public static function autoload($className) { // 从classMap里寻找 if (isset(static::$classMap[$className])) { $classFile = static::$classMap[$className]; if ($classFile[0] === '@') { $classFile = static::getAlias($classFile); } } elseif (strpos($className, '\\') !== false) { // 命名空间访问,先把命名空间的格式转成路径别名,例如: yii\base\Component 转成 @yii/base/Component.php $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?"); } }
简单总结下,这个函数规定了一些规则让php在寻找未知class时候可以include对应的文件,规则如下:
(1) 在classMap里面找
(2)如果class是使用命名空间访问的(例如:yii\base\Component), 会按照@yii/base/Component.php这样的路径去加载。
规则1
规则1说从classMap里面,那么classMap是什么呢,在入口文件里能找到Yii::$classMap = require(__DIR__ . '/classes.php');
,然后去看下classes.php是什么样子的:
return [ 'yii\base\Action' => YII2_PATH . '/base/Action.php', 'yii\base\ActionEvent' => YII2_PATH . '/base/ActionEvent.php', ..... ]
只是返回一个数组,key是class的名字,value是对应php文件的路径(YII2_PATH是预定于的常量,表示当前目录)。
if (isset(static::$classMap[$className])) {
所以Yii::autoload会先判断是否在classMap里面。
规则2(命名空间)
$classFile = static::getAlias('@' . str_replace('\\', '/', $className) . '.php', false);
函数会先把命名空间形式的调用转成对应的路径别名,再用getAlias函数转成对应的路径。
路径别名
再来看看static::getAlias()
这个函数是怎么把路径别名转成路径的。
public static $aliases = ['@yii' => __DIR__]; // 预设的路径别名映射数组
public static function getAlias($alias, $throwException = true) { // 检查是否有@前缀,没有的话直接返回。 if (strncmp($alias, '@', 1)) { // not an alias return $alias; } // 取出@alias部分赋值给root $pos = strpos($alias, '/'); $root = $pos === false ? $alias : substr($alias, 0, $pos); // 检查root是否在$aliases里是否有对应的alias if (isset(static::$aliases[$root])) { // 在$aliases里找到对应的@alias然后转化成实际的路径并返回 if (is_string(static::$aliases[$root])) { return $pos === false ? static::$aliases[$root] : static::$aliases[$root] . substr($alias, $pos); } // 如果找到的不是对应的路径字符串,就变量这个数组,看看里面有没有对应的 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"); } return false; }
创建Web应用实例
$config = require(__DIR__ . '/../config/web.php'); // 加载配置文件 (new yii\web\Application($config))->run();
到这里就开始涉及config配置的解析了,下一篇才慢慢分析。