以 yii 2.0.14 高级版的 frontend 为例,从 frontend/web/index.php 开始
//引用 yii2 composer 的 autoload,调用 getLoaderrequire __DIR__ . '/../../vendor/autoload.php';//引用 yii.phprequire __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 类 * 参考地址: * http://www.digpage.com/di.html * 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 中配置
$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) ,然后方法中执行
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
流程图
作者:smoke_zl
链接:https://www.jianshu.com/p/fb45b6ee6855