前言
本文主要分析Yii2应用的启动、运行的过程,主要包括以下三部分:入口脚本、启动应用、运行应用。
在分析源码的过程中主要借助了Xdebug工具。
入口脚本
文件位置:web\index.php
//定义全局变量
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');
//composer自动加载代码机制,可参考 https://segmentfault.com/a/1190000010788354
require(__DIR__ . '/../vendor/autoload.php');
//1.引入工具类Yii
//2.注册自动加载函数
//3.生成依赖注入中使用到的容器
require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php');
//加载应用配置
$config = require(__DIR__ . '/../config/web.php');
//生成应用并运行
(new yii\web\Application($config))->run();
分析: (new yii\web\Application($config))->run()
- 根据$config配置,生成应用实例(启动应用:new yii\web\Application($config))
- 运行应用实例(运行应用:yii\web\Application::run())
启动应用:new yii\web\Application($config)
分析:new yii\web\Application($config)
主要就是执行构造函数(代码位置:vendor\yiisoft\yii2\base\Application.php)
public function __construct($config = [])
{
//将Yii::$app指向当前的Application实例,因此后续就可以通过Yii::$app来调用应用
Yii::$app = $this;
//调用yii\base\Module::setInstance($instance)
//将Application实例本身记录为“已加载模块”
//详细参考本文后续“1-1分析”
static::setInstance($this);
//设置当前应用状态
$this->state = self::STATE_BEGIN;
//进行一些预处理(根据$config配置应用)
//详细代码位置:yii\base\Application::preInit(&$config)
//主要根据config文件做了以下这些事情
//1.判断$config中是否有配置项‘id’(必须有,否则抛异常)
//2.设置别名:@app,@vendor,@bower,@npm,@runtime
//3.设置时间区域(timeZone)
//4.自定义配置容器(Yii::$container)的属性(由这里我们知道可以自定义配置容器)
//5.合并核心组件配置到自定义组件配置:数组$config['components']
//(核心组件有哪些参考:yii\web\Application::coreComponents())
//(注意:这个方法中$config使用了引用,所以合并$config['components']可以改变$config原来的值)
$this->preInit($config);
//注册ErrorHandler,这样一旦抛出了异常或错误,就会由其负责处理
//代码位置:yii\base\Application::registerErrorHandler(&$config)
//详细参考本文后续“1-2分析”
$this->registerErrorHandler($config);
//根据$config配置Application
//然后执行yii\web\Application::init()(实际上是执行yii\base\Application::init())
//详细参考本文后续“1-3分析”
Component::__construct($config);
}
1-1分析:yii\base\Module::setInstance($instance)
public static function setInstance($instance)
{
if ($instance === null) {
unset(Yii::$app->loadedModules[get_called_class()]);
} else {
//将Application实例本身记录为“已加载模块”
Yii::$app->loadedModules[get_class($instance)] = $instance;
}
}
1-2分析:yii\base\Application::registerErrorHandler(&$config)
//yii\web\Application的 $errorHandler 对应 yii\web\ErrorHandler(由yii\web\Application::coreComponents()得知)
//而yii\web\ErrorHandler继承自yii\base\ErrorHandler
protected function registerErrorHandler(&$config)
{
if (YII_ENABLE_ERROR_HANDLER) {
if (!isset($config['components']['errorHandler']['class'])) {
echo "Error: no errorHandler component is configured.\n";
exit(1);
}
//可以看到就是根据$config['components']['errorHandler']来配置
$this->set('errorHandler', $config['components']['errorHandler']);
unset($config['components']['errorHandler']);
//通过PHP函数注册error handler(具体参考下面的yii\base\ErrorHandler::register())
$this->getErrorHandler()->register();
}
}
//默认config文件关于error handler的配置,这里的配置会合并到Yii2本身对核心组件的定义中去(),核心组件的定义在yii\web\Application::coreComponents()
'components' => [
'errorHandler' => [
'errorAction' => 'site/error',
],
],
//yii\base\ErrorHandler::register()
public function register()
{
//该选项设置是否将错误信息作为输出的一部分显示到屏幕,
//或者对用户隐藏而不显示。
ini_set('display_errors', false);
//当抛出异常且没有被catch的时候,由yii\base\ErrorHandler::handleException()处理
set_exception_handler([$this, 'handleException']);
if (defined('HHVM_VERSION')) {
set_error_handler([$this, 'handleHhvmError']);
} else {
//当出现error的时候由yii\base\ErrorHandler::handleError()处理
set_error_handler([$this, 'handleError']);
}
if ($this->memoryReserveSize > 0) {
$this->_memoryReserve = str_repeat('x', $this->memoryReserveSize);
}
////当出现fatal error的时候由yii\base\ErrorHandler::handleFatalError()处理
register_shutdown_function([$this, 'handleFatalError']);
}
一个值得注意的地方:
在handleException()、handleError()、handleFatalError()中会直接或间接调用yii\web\ErrorHandle::renderException(),而在这个函数里面,有以下这一行代码
$result = Yii::$app->runAction($this->errorAction);
回顾上面config文件中对errorAction这个属性的定义,我们知道可以通过自定义一个action用于在异常或错误时显示自定义的出错页面
例如yii2中默认使用’site/error’这个action来处理
1-3分析:Component::__construct($config)
(代码实际位置:vendor\yiisoft\yii2\base\Object.php)
public function __construct($config = [])
{
if (!empty($config)) {
//见下面的详细分析
Yii::configure($this, $config);
}
//跳转到yii\base\Application::init(),见下面的分析
$this->init();
}
//Yii::configure($object, $properties)分析
//实际上就是为Application的属性赋值
//注意:这里会触发一些魔术方法,间接调用了:
//yii\web\Application::setHomeUrl()
//yii\di\ServiceLocator::setComponents()(这个值得注意,详细参考本文后续“1-3-1分析”)
//yii\base\Module::setModules()
public static function configure($object, $properties)
{
foreach ($properties as $name => $value) {
$object->$name = $value;
}
return $object;
}
yii\base\Application::init()分析
public function init()
{
//设置当前应用状态
$this->state = self::STATE_INIT;
//见下面的bootstrap()
$this->bootstrap();
}
protected function bootstrap()
{
//request是核心组件,这里使用了依赖注入机制(ServiceLocator)来获取request
//注意:这里是第一次调用request组件,会初始化该组件
$request = $this->getRequest();
//设置别名
Yii::setAlias('@webroot', dirname($request->getScriptFile()));
Yii::setAlias('@web', $request->getBaseUrl());
//跳转到yii\base\Application::bootstrap(),详细参考本文后续“1-3-2分析”
//在这里主要是处理第三方扩展(extensions)和一些预启动(bootstrap)
parent::bootstrap();
}
1-3-1分析:yii\di\ServiceLocator::setComponents()
public function setComponents($components)
{
foreach ($components as $id => $component) {
//设置各个组件的定义,这样后续就能通过依赖注入机制来获取获取组件了
//应该还记得这里的配置参数来源于config文件中的定义和yii\web\Application::coreComponents()中的定义
$this->set($id, $component);
}
}
1-3-2分析:yii\base\Application::bootstrap()
protected function bootstrap()
{
if ($this->extensions === null) {
//@vendor/yiisoft/extensions.php是一些关于第三方扩展的配置,当用composer require安装第三扩展的时候就会将新的扩展的相关信息记录到该文件,这样我们就可以在代码中调用了
$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__);
}
}
}
//预启动,通常会包括‘log’组件,‘debug’模块和‘gii’模块(参考配置文件)
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__);
}
}
}
在完成了上面的流程之后,应用就算启动成功了,可以开始运行,处理请求了。
运行应用:yii\web\Application::run()
分析:yii\base\Application::run()
在上面的构造函数执行完后,开始运行应用。即下面这行代码的run()部分
(new yii\web\Application($config))->run();//(实际上执行的是yii\base\Application::run())
public function run()
{
try {
$this->state = self::STATE_BEFORE_REQUEST;
//触发事件’beforeRequest’,依次执行该事件的handler,
$this->trigger(self::EVENT_BEFORE_REQUEST);
$this->state = self::STATE_HANDLING_REQUEST;
//处理请求,这里的返回值是yii\web\Response实例
//handleRequest(),详细参考本文后续“2-1分析”
$response = $this->handleRequest($this->getRequest());
$this->state = self::STATE_AFTER_REQUEST;
//触发事件’afterRequest’,依次执行该事件的handler
$this->trigger(self::EVENT_AFTER_REQUEST);
$this->state = self::STATE_SENDING_RESPONSE;
//发送响应,详细参考本文后续“2-2分析”
$response->send();
$this->state = self::STATE_END;
return $response->exitStatus;
} catch (ExitException $e) {
//结束运行
$this->end($e->statusCode, isset($response) ? $response : null);
return $e->statusCode;
}
}
2-1分析: yii\web\Application::handleRequest()
public function handleRequest($request)
{
if (empty($this->catchAll)) {
try {
//解析请求得到路由和相应的参数,这里会调用urlManager组件来处理
list ($route, $params) = $request->resolve();
} catch (UrlNormalizerRedirectException $e) {
$url = $e->url;
if (is_array($url)) {
if (isset($url[0])) {
// ensure the route is absolute
$url[0] = '/' . ltrim($url[0], '/');
}
$url += $request->getQueryParams();
}
//当解析请求出现异常时进行重定向
return $this->getResponse()->redirect(Url::to($url, $e->scheme), $e->statusCode);
}
} else {
//’catchAll’参数可以在配置文件中自定义,可用于在项目需要临时下线维护时给出一个统一的访问路由
$route = $this->catchAll[0];
$params = $this->catchAll;
unset($params[0]);
}
try {
Yii::trace("Route requested: '$route'", __METHOD__);
//记录下当前请求的route
$this->requestedRoute = $route;
//执行路由相对应的action(yii\base\Module::runAction()详细参考本文后续“2-1-1分析”)
$result = $this->runAction($route, $params);
if ($result instanceof Response) {
return $result;
} else {
//如果action的返回结果不是Response的实例,则将结果封装到Response实例的data属性中
$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);
}
}
2-1-1分析: yii\base\Module::runAction()
public function runAction($route, $params = [])
{
//根据路由创建Controller实例(详细参考本文后续“2-1-1-1分析”)
$parts = $this->createController($route);
if (is_array($parts)) {
/* @var $controller Controller */
list($controller, $actionID) = $parts;
$oldController = Yii::$app->controller;
//设置当前的Controller实例
Yii::$app->controller = $controller;
//执行action(yii\base\Controller::runAction()详细参考本文后续“2-1-1-2分析”)
$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) . '".');
}
2-1-1-1分析: yii\base\Module::createController()
public function createController($route)
{
//如果route为空则设置route为默认路由,这个可以在配置文件中自定义
if ($route === '') {
$route = $this->defaultRoute;
}
// double slashes or leading/ending slashes may cause substr problem
$route = trim($route, '/');
if (strpos($route, '//') !== false) {
return false;
}
if (strpos($route, '/') !== false) {
list ($id, $route) = explode('/', $route, 2);
} else {
$id = $route;
$route = '';
}
//优先使用controllerMap,controllerMap可以如下
/*
[
'account' => 'app\controllers\UserController',
'article' => [
'class' => 'app\controllers\PostController',
'pageTitle' => 'something new',
],
]
*/
// module and controller map take precedence
if (isset($this->controllerMap[$id])) {
$controller = Yii::createObject($this->controllerMap[$id], [$id, $this]);
return [$controller, $route];
}
//先判断是否存在相应的模块
$module = $this->getModule($id);
if ($module !== null) {
//当存在模块时,进行递归
return $module->createController($route);
}
if (($pos = strrpos($route, '/')) !== false) {
$id .= '/' . substr($route, 0, $pos);
$route = substr($route, $pos + 1);
}
//最终找到Controller的id
$controller = $this->createControllerByID($id);
if ($controller === null && $route !== '') {
//详细见下面的代码分析
$controller = $this->createControllerByID($id . '/' . $route);
$route = '';
}
//返回Controller实例和剩下的路由信息
return $controller === null ? false : [$controller, $route];
}
public function createControllerByID($id)
{
$pos = strrpos($id, '/');
if ($pos === false) {
$prefix = '';
$className = $id;
} else {
$prefix = substr($id, 0, $pos + 1);
$className = substr($id, $pos + 1);
}
if (!preg_match('%^[a-z][a-z0-9\\-_]*$%', $className)) {
return null;
}
if ($prefix !== '' && !preg_match('%^[a-z0-9_/]+$%i', $prefix)) {
return null;
}
//进行一些转换
$className = str_replace(' ', '', ucwords(str_replace('-', ' ', $className))) . 'Controller';
$className = ltrim($this->controllerNamespace . '\\' . str_replace('/', '\\', $prefix) . $className, '\\');
if (strpos($className, '-') !== false || !class_exists($className)) {
return null;
}
if (is_subclass_of($className, 'yii\base\Controller')) {
//通过依赖注入容器获得Controller实例
$controller = Yii::createObject($className, [$id, $this]);
return get_class($controller) === $className ? $controller : null;
} elseif (YII_DEBUG) {
throw new InvalidConfigException("Controller class must extend from \\yii\\base\\Controller.");
}
return null;
}
2-1-1-2分析:yii\base\Controller::runAction()
public function runAction($id, $params = [])
{
//创建action实例,详细见下面的代码
$action = $this->createAction($id);
if ($action === null) {
throw new InvalidRouteException('Unable to resolve the request: ' . $this->getUniqueId() . '/' . $id);
}
Yii::trace('Route to run: ' . $action->getUniqueId(), __METHOD__);
if (Yii::$app->requestedAction === null) {
Yii::$app->requestedAction = $action;
}
$oldAction = $this->action;
$this->action = $action;
$modules = [];
$runAction = true;
//返回的modules包括该controller当前所在的module,以及该module的所有祖先module(递归直至没有祖先module)
//然后从最初的祖先module开始,依次执行“模块级”的beforeActio()
//如果有beforeAction()没有返回true, 那么会中断后续的执行
// call beforeAction on modules
foreach ($this->getModules() as $module) {
if ($module->beforeAction($action)) {
array_unshift($modules, $module);
} else {
$runAction = false;
break;
}
}
$result = null;
//执行当前控制器的beforeAction,通过后再最终执行action
//(如果前面“模块级beforeAction”没有全部返回true,则这里不会执行)
if ($runAction && $this->beforeAction($action)) {
// run the action
//(代码位置:yii\base\InlineAction::runWithParams()详细参考本文后续“2-1-1-2-1分析”和yii\base\Action::runWithParams()详细参考本文后续“2-1-1-2-2分析”)
$result = $action->runWithParams($params);
//执行当前Controller的afterAction
$result = $this->afterAction($action, $result);
//从当前模块开始,执行afterAction,直至所有祖先的afterAction
// call afterAction on modules
foreach ($modules as $module) {
/* @var $module Module */
$result = $module->afterAction($action, $result);
}
}
if ($oldAction !== null) {
$this->action = $oldAction;
}
//如果有beforeAction没有通过,那么会返回null
return $result;
}
public function createAction($id)
{
//默认action
if ($id === '') {
$id = $this->defaultAction;
}
//独立action(Standalone Actions )
$actionMap = $this->actions();
if (isset($actionMap[$id])) {
//返回一个action实例,通常是yii\base\Action的子类
return Yii::createObject($actionMap[$id], [$id, $this]);
} elseif (preg_match('/^[a-z0-9\\-_]+$/', $id) && strpos($id, '--') === false && trim($id, '-') === $id) {
$methodName = 'action' . str_replace(' ', '', ucwords(implode(' ', explode('-', $id))));
if (method_exists($this, $methodName)) {
$method = new \ReflectionMethod($this, $methodName);
if ($method->isPublic() && $method->getName() === $methodName) {
//InlineAction封装了将要执行的action的相关信息,该类继承自yii\base\Action
return new InlineAction($id, $this, $methodName);
}
}
}
return null;
}
2-1-1-2-1分析: yii\base\InlineAction::runWithParams()
public function runWithParams($params)
{
//yii\web\Controller::bindActionParams()详细参考本文后续“2-1-1-2-1-1分析”
$args = $this->controller->bindActionParams($this, $params);
Yii::trace('Running action: ' . get_class($this->controller) . '::' . $this->actionMethod . '()', __METHOD__);
if (Yii::$app->requestedParams === null) {
Yii::$app->requestedParams = $args;
}
//真正地调用开发者写的Action代码
return call_user_func_array([$this->controller, $this->actionMethod], $args);
}
2-1-1-2-2分析:yii\base\Action::runWithParams()
public function runWithParams($params)
{
if (!method_exists($this, 'run')) {
throw new InvalidConfigException(get_class($this) . ' must define a "run()" method.');
}
//yii\web\Controller::bindActionParams()详细参考本文后续“2-1-1-2-1-1分析”
$args = $this->controller->bindActionParams($this, $params);
Yii::trace('Running action: ' . get_class($this) . '::run()', __METHOD__);
if (Yii::$app->requestedParams === null) {
Yii::$app->requestedParams = $args;
}
if ($this->beforeRun()) {
//执行独立Action的run方法
$result = call_user_func_array([$this, 'run'], $args);
$this->afterRun();
return $result;
} else {
return null;
}
}
2-1-1-2-1-1分析:yii\web\Controller::bindActionParams()
public function bindActionParams($action, $params)
{
if ($action instanceof InlineAction) {
//如果是InlineAction则对Controller中相应的action方法进行反射
$method = new \ReflectionMethod($this, $action->actionMethod);
} else {
//如果是独立action则对该Action类的run方法进行反射
$method = new \ReflectionMethod($action, 'run');
}
$args = [];
$missing = [];
$actionParams = [];
//通过php提供的反射机制绑定Action的参数,同时还会判断url中的参数是否满足要求
foreach ($method->getParameters() as $param) {
$name = $param->getName();
if (array_key_exists($name, $params)) {
if ($param->isArray()) {
$args[] = $actionParams[$name] = (array) $params[$name];
} elseif (!is_array($params[$name])) {
$args[] = $actionParams[$name] = $params[$name];
} else {
throw new BadRequestHttpException(Yii::t('yii', 'Invalid data received for parameter "{param}".', [
'param' => $name,
]));
}
unset($params[$name]);
} elseif ($param->isDefaultValueAvailable()) {
$args[] = $actionParams[$name] = $param->getDefaultValue();
} else {
$missing[] = $name;
}
}
if (!empty($missing)) {
throw new BadRequestHttpException(Yii::t('yii', 'Missing required parameters: {params}', [
'params' => implode(', ', $missing),
]));
}
$this->actionParams = $actionParams;
return $args;
}
2-2分析: yii\web\Response::send()
public function send()
{
if ($this->isSent) {
return;
}
$this->trigger(self::EVENT_BEFORE_SEND);
//预处理,详见下面的代码
$this->prepare();
$this->trigger(self::EVENT_AFTER_PREPARE);
//发送http响应的头部,详见下面的代码
$this->sendHeaders();
//发送http响应的主体,详见下面的代码
$this->sendContent();
$this->trigger(self::EVENT_AFTER_SEND);
$this->isSent = true;
}
protected function prepare()
{
if ($this->stream !== null) {
return;
}
//使用formatter对相应进行处理,这个可以在配置文件中自定义
if (isset($this->formatters[$this->format])) {
$formatter = $this->formatters[$this->format];
if (!is_object($formatter)) {
$this->formatters[$this->format] = $formatter = Yii::createObject($formatter);
}
if ($formatter instanceof ResponseFormatterInterface) {
$formatter->format($this);
} else {
throw new InvalidConfigException("The '{$this->format}' response formatter is invalid. It must implement the ResponseFormatterInterface.");
}
} elseif ($this->format === self::FORMAT_RAW) {
if ($this->data !== null) {
$this->content = $this->data;
}
} else {
throw new InvalidConfigException("Unsupported response format: {$this->format}");
}
//确保响应的content为string
if (is_array($this->content)) {
throw new InvalidParamException('Response content must not be an array.');
} elseif (is_object($this->content)) {
if (method_exists($this->content, '__toString')) {
$this->content = $this->content->__toString();
} else {
throw new InvalidParamException('Response content must be a string or an object implementing __toString().');
}
}
}
protected function sendHeaders()
{
//判断是否已经把头部发送出去了
if (headers_sent()) {
return;
}
if ($this->_headers) {
$headers = $this->getHeaders();
//设置并发送http响应头
foreach ($headers as $name => $values) {
$name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $name)));
// set replace for first occurrence of header but false afterwards to allow multiple
$replace = true;
foreach ($values as $value) {
//主要就是调用了PHP的header()函数
header("$name: $value", $replace);
$replace = false;
}
}
}
$statusCode = $this->getStatusCode();
header("HTTP/{$this->version} {$statusCode} {$this->statusText}");
//主要就是调用了PHP的setcookie()函数
$this->sendCookies();
}
protected function sendContent()
{
if ($this->stream === null) {
//直接echo输出内容
echo $this->content;
return;
}
set_time_limit(0); // Reset time limit for big files
$chunkSize = 8 * 1024 * 1024; // 8MB per chunk
if (is_array($this->stream)) {
list ($handle, $begin, $end) = $this->stream;
fseek($handle, $begin);
while (!feof($handle) && ($pos = ftell($handle)) <= $end) {
if ($pos + $chunkSize > $end) {
$chunkSize = $end - $pos + 1;
}
echo fread($handle, $chunkSize);
flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
}
fclose($handle);
} else {
while (!feof($this->stream)) {
echo fread($this->stream, $chunkSize);
flush();
}
fclose($this->stream);
}
}
请求流程
声明:以下内容转载自‘http://www.yiichina.com/doc/g...’
1.用户向入口脚本 web/index.php 发起请求。
2.入口脚本加载应用配置并创建一个应用实例去处理请求。
3.应用通过请求组件解析请求的路由。
4.应用创建一个控制器实例去处理请求。
5.控制器创建一个操作实例并针对操作执行过滤器。
6.如果任何一个过滤器返回失败,则操作退出。
7.如果所有过滤器都通过,操作将被执行。
8.操作会加载一个数据模型,或许是来自数据库。
9.操作会渲染一个视图,把数据模型提供给它。
10.渲染结果返回给响应组件。
11.响应组件发送渲染结果给用户浏览器。
应用主体生命周期
声明:以下内容转载自"http://www.yiichina.com/doc/guide/2.0/structure-applications"
在深入研究了源码之后,在此总结一下。当运行入口脚本处理请求时, 应用主体会经历以下生命周期:
- 入口脚本加载应用主体配置数组。
-
入口脚本创建一个应用主体实例:
a.调用 preInit() 配置几个高级别应用主体属性, 比如 yii\\base\\Application::basePath。 b.注册 yii\\base\\Application::errorHandler 错误处理方法. c.配置应用主体属性. d.调用 init() 初始化,该函数会调用 bootstrap() 运行引导启动组件.
-
入口脚本调用 yii\base\Application::run() 运行应用主体:
a.触发 EVENT_BEFORE_REQUEST 事件。 b.处理请求:解析请求 路由 和相关参数; 创建路由指定的模块、控制器和动作对应的类,并运行动作。 c.触发 EVENT_AFTER_REQUEST 事件。 d.发送响应到终端用户.
- 入口脚本接收应用主体传来的退出状态并完成请求的处理。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。