以 yii 2.0.14 高级版的 frontend 为例,从 frontend/web/index.php 开始
//引用 yii2 composer 的 autoload,调用 getLoader require __DIR__ . '/../../vendor/autoload.php'; //引用 yii.php require __DIR__ . '/../../vendor/yiisoft/yii2/Yii.php'; //引用 bootstrap.php 定义一些别名等 require __DIR__ . '/../../common/config/bootstrap.php'; require __DIR__ . '/../config/bootstrap.php';
//合并配置文件
$config = yii\helpers\ArrayHelper::merge(
require DIR . '/../../common/config/main.php',
require DIR . '/../../common/config/main-local.php',
require DIR . '/../config/main.php',
require DIR . '/../config/main-local.php'
);
(new yii\web\Application($config))->run();
入口文件看着就这么几行,简单的很,那他是怎么通过这几行来运行应用的呢?先看 Yii.php 内的逻辑
/**
-
Yii::autoload 内执行过程
-
1、先查看类是否在 Yii::$classMap 中存在,存在直接调用 getAlias 生成类文件物理地址
-
2、如果 Yii::$classMap 中不存在,将命名空间转为实际路径调用 getAlias 生成类文件物理地址
/
spl_autoload_register(['Yii', 'autoload'], true, true);
//yii2 核心类的类名和物理文件地址映射的 hash 数组
Yii::$classMap = require DIR . '/classes.php';
/* -
实例化 依赖注入(Dependency Injection,DI)容器
-
依赖注入容器知道怎样初始化并配置对象及其依赖的所有对象
-
在Yii中使用DI解耦,有2种注入方式:构造函数注入、属性注入
-
yii\di\Container 继承了
-
yii\base\Component
-
yii\base\BaseObject
-
BaseObject 实现了 Configurable
-
DI容器只支持 yii\base\Object 类
-
如果你的类想放在DI容器里,那么必须继承自 yii\base\Object 类
-
参考地址:
https://www.cnblogs.com/minirice/p/yii2_configurations.html
/
Yii::$container = new yii\di\Container();
接下来,就是重头戏,yii\web\Application,它继承了
yii\base\Application
yii\base\Module
yii\di\ServiceLocator(服务定位器)
yii\base\Component
yii\base\BaseObject, BaseObject 实现 Configurable
PS:继承 Component 的都有 on event 和 as behavior 配置实现事件绑定
一、new yii\web\Application 时,会调用构造方法 yii\base\Application::__construct
public function __construct($config = [])
{
Yii::app = this;
//application 对象放到注册树中
static::setInstance($this);
$this->state = self::STATE_BEGIN;
/*
- 初始化 application 中应用属性的一些值,配置一些高优先级的应用属性
- 还会初始化 components 中,log、user、urlManager 对应的类文件
- 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'];
}
- }
- yii\web\Application 中,coreComponents 的代码
- public function coreComponents()
- {
return array_merge(parent::coreComponents(), [
'request' => ['class' => 'yii\web\Request'],
'response' => ['class' => 'yii\web\Response'],
'session' => ['class' => 'yii\web\Session'],
'user' => ['class' => 'yii\web\User'],
'errorHandler' => ['class' => 'yii\web\ErrorHandler'],
]);
- }
- yii\base\Application 中,coreComponents 的代码
- public function coreComponents()
- {
return [
'log' => ['class' => 'yii\log\Dispatcher'],
'view' => ['class' => 'yii\web\View'],
'formatter' => ['class' => 'yii\i18n\Formatter'],
'i18n' => ['class' => 'yii\i18n\I18N'],
'mailer' => ['class' => 'yii\swiftmailer\Mailer'],
'urlManager' => ['class' => 'yii\web\UrlManager'],
'assetManager' => ['class' => 'yii\web\AssetManager'],
'security' => ['class' => 'yii\base\Security'],
];
- }
- 从2.0.11 开始,配置支持使用 container 属性来配置依赖注入容器
- 'container' => [
'definitions' => [
'yii\widgets\LinkPager' => ['maxButtonCount' => 5]
],
'singletons' => [
// 依赖注入容器单例配置
]
- ]
/
this->preInit(config);
/*
-
registerErrorHandler 内代码
-
1、调用 this->set('errorHandler', config['components']['errorHandler'])
-
将 errorHandler 配置放到 ServiceLocator (_definitions 数组中,这时还没实例化)
-
2、调用 $this->getErrorHandler()->register()
-
调用 getErrorHandler,使用 createObject 调用 Container 依赖注入容器实例化对象
-
调用 yii\web\ErrorHandler::register,初始化错误异常显示和抛出
/
this->registerErrorHandler(config);
/* -
在多层继承中,调用上级某一层的构造函数,而不是单纯的父类构造函数
-
上级某一层的构造函数中如果调用了某个方法
-
并且这个方法被下层类重写过,那么会直接执行重写之后的方法
-
所以执行 Component::__construct,__construct 中调用 init()
-
会执行 yii\base\Application 的 init
-
如果上级调用下级重写的 静态方法 时
要使用延时静态绑定(上级静态调用 self::a() 改为 static::a())
/
Component::__construct($config);
}
二、yii\base\Application::init 代码
public function init()
{
$this->state = self::STATE_INIT;
$this->bootstrap();
}
三、yii\web\Application::bootstrap 代码
protected function bootstrap()
{
/*
通过 Application::get('request')
使用 createObject 实现调用 Container 依赖注入容器实例化对象
/
request = this->getRequest();
//定义别名
Yii::setAlias('@webroot', dirname($request->getScriptFile()));
Yii::setAlias('@web', $request->getBaseUrl());
//调用 yii\base\Application::bootstrap 代码
parent::bootstrap();
}
四、yii\base\Application::bootstrap 代码太多,不展示源码了,大致总结为
1、是否在配置文件中配置了 extensions 参数,如果没有配置,直接加载扩展清单文件 @vendor/yiisoft/extensions.php,否则使用配置的 extensions。然后在 extensions 文件返回的数组中,可有含有 alias 和 bootstrap 参数,根据 alias 中的参数定义别名,根据 bootstrap 中的参数,使用 createObject 实例化对象(创建并运行各个扩展声明的 引导组件 )
2、根据配置文件配置的 bootstrap 参数,使用 createObject 实例化对象(创建并运行各个 应用组件 以及在应用的 bootstrap 属性中声明的各个 模块组件 )
3、注意:extensions 文件中配置的 bootstrap 和 配置文件中配置的 bootstrap,如果实现了 BootstrapInterface 接口,还会执行实例化后的 bootstrap 方法
4、注意:bootstrap 会直接将配置的类实例化,而不是在第一次使用的时候实例化,所以为了性能考虑 bootstrap 中的配置应该尽量少,而且只配置一些全局使用的类
五、yii\base\Application::run 代码
public function run()
{
try {
$this->state = self::STATE_BEFORE_REQUEST;
/*
- trigger 触发通知,将此事件通知给绑定到这个事件的观察者,绑定事件的方法:
- yii\base\Component 或者其子类::on("事件名称","方法")
*/
$this->trigger(self::EVENT_BEFORE_REQUEST);$this->state = self::STATE_HANDLING_REQUEST;
response = this->handleRequest($this->getRequest());$this->state = self::STATE_AFTER_REQUEST;
$this->trigger(self::EVENT_AFTER_REQUEST);$this->state = self::STATE_SENDING_RESPONSE;
$response->send();$this->state = self::STATE_END;
return $response->exitStatus;
} catch (ExitException $e) {
this->end(e->statusCode, isset(response) ? response : null);
return $e->statusCode;
}
}
六、yii\web\Application::handleRequest 代码
public function handleRequest($request)
{
if (empty($this->catchAll)) {
try {
//resolve 方法调用 urlManager 对 url 进行解析
list(route, params) = $request->resolve();
} catch (UrlNormalizerRedirectException $e) {
url = e->url;
if (is_array($url)) {
if (isset($url[0])) {
url[0] = '/' . ltrim(url[0], '/');
}
url += request->getQueryParams();
}return $this->getResponse()->redirect(Url::to($url, $e->scheme), $e->statusCode);
}
} else {
/**
- 如果设置了 catchAll 变量, 那么所有请求都会跳转到这里
- 示例:
- 假设网站维护, 需要将网站重定向到一个设置好的页面上
- 可以在配置文件中添加
- 'catchAll' => ['offline/index']
- 这样, 所有的访问都跳转到 offline/index 页面了
*/
route = this->catchAll[0];
params = this->catchAll;
unset($params[0]);
}
try {
Yii::debug("Route requested: '$route'", METHOD);
this->requestedRoute = route;
//根据 route 访问对应的 module/controller/action
result = this->runAction(route, params);
if ($result instanceof Response) {
return $result;
}response = this->getResponse();
if ($result !== null) {
response->data = result;
}
return $response;
} catch (InvalidRouteException $e) {
throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'), e->getCode(), e);
}
}
七、yii\base\Module::runAction 代码
public function runAction(route, params = [])
{
/**yii\base\Module::createController 代码也不贴了,可以追进去看,思路是
1、如果 route 是空(直接通过域名访问应用 www.aaa.com)
使用配置中的 defaultRoute 属性
2、route 不为空,查看配置文件中是否有 controllerMap 的配置
直接使用配置创建
controllerMap 配置如
[
'controllerMap' => [
// 用类名申明 "account" 控制器
'account' => 'app\controllers\UserController',
// 用配置数组申明 "article" 控制器
'article' => [
'class' => 'app\controllers\PostController',
'enableCsrfValidation' => false,
]
]
]
3、调用 yii/base/Module::getModule 查看 route 中是否有 module 存在
如果直接调用yii/base/Module::createController 方法
否则调用 yii/base/Module::createControllerByID
通过 createControllerByID 实例化的 Controller 类,必须继承 yii\base\Controller
createController 和 createControllerByID 都使用 Yii::createObject 实例化
*/
parts = this->createController($route);
if (is_array($parts)) {
list(controller, actionID) = $parts;
oldController = Yii::app->controller;
Yii::app->controller = controller;
result = controller->runAction(actionID, params);
if ($oldController !== null) {
Yii::app->controller = oldController;
}return $result;
}
id = this->getUniqueId();
throw new InvalidRouteException('Unable to resolve the request "' . (id === '' ? route : id . '/' . route) . '".');
}
八、说明一下 yii/base/Module::getModule 这个很有意思
1、先看一下配置文件时 modules 配置后的赋值过程
我们使用 modules 时,需要在配置文件中配置 modules,比如
'modules' => [
'v1' => [
'class' => 'frontend\modules\v1\Module',
],
],
或者像 main-local.php 中那样,新建一个 config,配置完以后 return config,config 中配置</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">config['modules']['gii'] = [
'class' => 'yii\gii\Module',
];
这个 modules 的属性,在 Application 及其父类中,都是不存在的
只有私有属性 $_modules,存在于 yii\base\Module 类中
当 new yii\web\Application 执行 yii\base\Application::construct 方法时
方法中执行了 Component::construct($config) (不清楚的往上看,上边有这块代码)
然后 Component::construct($config) 实际执行的是
BaseObject::construct(config) ,然后方法中执行</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">if (!empty(config)) {
Yii::configure(this, config);
}
再调用 yii\base\Component::setter 方法 (yii\base\Module::setModules),将 $_modules 赋值
2、如果 module 套着 module,需要这么这么设置
'modules' => [
'v1' => [
'class' => 'frontend\modules\v1\Module',
'modules' => [
'v2' => 'frontend\modules\v2\Module'
],
],
],
九、yii\base\Controller::runAction 代码
public function runAction(id, params = [])
{
/**
- yii\base\Controller::createAction 代码也不贴了,可以追进去看,思路是
- 1、如果 action id 是空(访问 www.aaa.com/controller)
- 使用 yii\base\Controller 中的 defaultAction 属性
- 2、id 不为空,查看 Controller::actions 方法中是否有配置
- 如果有,直接使用配置创建,actions 配置如
- public function actions()
- {
return [
'error' => [
'class' => 'yii\web\ErrorAction',
],
'captcha' => [
'class' => 'yii\captcha\CaptchaAction',
'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
],
];
- }
- 3、利用反射(ReflectionMethod)查看调用方法是否存在,是否是公共方法
- 如果是,返回 yii\base\InlineAction 的实例
*/
action = this->createAction($id);
if ($action === null) {
throw new InvalidRouteException('Unable to resolve the request: ' . this->getUniqueId() . '/' . id);
}
Yii::debug('Route to run: ' . $action->getUniqueId(), METHOD);
if (Yii::$app->requestedAction === null) {
Yii::app->requestedAction = action;
}oldAction = this->action;
this->action = action;$modules = [];
$runAction = true;
//调用所有加载模块中的 beforeAction 方法
foreach (this->getModules() as module) {
if (module->beforeAction(action)) {
array_unshift(modules, module);
} else {
$runAction = false;
break;
}
}$result = null;
if (runAction && this->beforeAction($action)) {
$result = $action->runWithParams($params); $result = $this->afterAction($action, $result); //调用所有加载模块中的 afterAction 方法 foreach ($modules as $module) { $result = $module->afterAction($action, $result); }
}
if ($oldAction !== null) {
this->action = oldAction;
}
return $result;
}
最后,附个图,源自
http://www.yiichina.com/doc/guide/2.0/structure-applications
G
M
T
Detect languageAfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBengaliBosnianBulgarianCatalanCebuanoChichewaChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDutchEnglishEsperantoEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekGujaratiHaitian CreoleHausaHebrewHindiHmongHungarianIcelandicIgboIndonesianIrishItalianJapaneseJavaneseKannadaKazakhKhmerKoreanLaoLatinLatvianLithuanianMacedonianMalagasyMalayMalayalamMalteseMaoriMarathiMongolianMyanmar (Burmese)NepaliNorwegianPersianPolishPortuguesePunjabiRomanianRussianSerbianSesothoSinhalaSlovakSlovenianSomaliSpanishSundaneseSwahiliSwedishTajikTamilTeluguThaiTurkishUkrainianUrduUzbekVietnameseWelshYiddishYorubaZulu | AfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBengaliBosnianBulgarianCatalanCebuanoChichewaChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDutchEnglishEsperantoEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekGujaratiHaitian CreoleHausaHebrewHindiHmongHungarianIcelandicIgboIndonesianIrishItalianJapaneseJavaneseKannadaKazakhKhmerKoreanLaoLatinLatvianLithuanianMacedonianMalagasyMalayMalayalamMalteseMaoriMarathiMongolianMyanmar (Burmese)NepaliNorwegianPersianPolishPortuguesePunjabiRomanianRussianSerbianSesothoSinhalaSlovakSlovenianSomaliSpanishSundaneseSwahiliSwedishTajikTamilTeluguThaiTurkishUkrainianUrduUzbekVietnameseWelshYiddishYorubaZulu |
---|
Text-to-speech function is limited to 200 characters
Options : History : Feedback : Donate | Close |
---|