最近在看 yii2 源碼,想在入門的基礎(chǔ)上掌握 yii2 更多的設(shè)計(jì)思路和設(shè)計(jì)風(fēng)格,以便更好的理解 yii2
以 yii 2.0.14 高級(jí)版的 frontend 為例郎仆,從 frontend/web/index.php 開始
//引用 yii2 composer 的 autoload台颠,調(diào)用 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();
入口文件看著就這么幾行,簡單的很蜕琴,那他是怎么通過這幾行來運(yùn)行應(yīng)用的呢?先看 Yii.php 內(nèi)的邏輯
/**
* Yii::autoload 內(nèi)執(zhí)行過程
* 1宵溅、先查看類是否在 Yii::$classMap 中存在凌简,存在直接調(diào)用 getAlias 生成類文件物理地址
* 2、如果 Yii::$classMap 中不存在恃逻,將命名空間轉(zhuǎn)為實(shí)際路徑調(diào)用 getAlias 生成類文件物理地址
*/
spl_autoload_register(['Yii', 'autoload'], true, true);
//yii2 核心類的類名和物理文件地址映射的 hash 數(shù)組
Yii::$classMap = require __DIR__ . '/classes.php';
/**
* 實(shí)例化 依賴注入(Dependency Injection雏搂,DI)容器
* 依賴注入容器知道怎樣初始化并配置對(duì)象及其依賴的所有對(duì)象
* 在Yii中使用DI解耦,有2種注入方式:構(gòu)造函數(shù)注入辛块、屬性注入
* yii\di\Container 繼承了
* yii\base\Component
* yii\base\BaseObject
* BaseObject 實(shí)現(xiàn)了 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(服務(wù)定位器)
yii\base\Component
yii\base\BaseObject, BaseObject 實(shí)現(xiàn) Configurable
PS:繼承 Component 的都有 on event 和 as behavior 配置實(shí)現(xiàn)事件綁定
一、new yii\web\Application 時(shí)尘盼,會(huì)調(diào)用構(gòu)造方法 yii\base\Application::__construct
public function __construct($config = [])
{
Yii::$app = $this;
//application 對(duì)象放到注冊樹中
static::setInstance($this);
$this->state = self::STATE_BEGIN;
/**
* 初始化 application 中應(yīng)用屬性的一些值憨愉,配置一些高優(yōu)先級(jí)的應(yīng)用屬性
* 還會(huì)初始化 components 中烦绳,log、user配紫、urlManager 對(duì)應(yīng)的類文件
* 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 內(nèi)代碼
* 1享扔、調(diào)用 $this->set('errorHandler', $config['components']['errorHandler'])
* 將 errorHandler 配置放到 ServiceLocator (_definitions 數(shù)組中,這時(shí)還沒實(shí)例化)
* 2植袍、調(diào)用 $this->getErrorHandler()->register()
* 調(diào)用 getErrorHandler惧眠,使用 createObject 調(diào)用 Container 依賴注入容器實(shí)例化對(duì)象
* 調(diào)用 yii\web\ErrorHandler::register,初始化錯(cuò)誤異常顯示和拋出
*/
$this->registerErrorHandler($config);
/**
* 在多層繼承中于个,調(diào)用上級(jí)某一層的構(gòu)造函數(shù)氛魁,而不是單純的父類構(gòu)造函數(shù)
* 上級(jí)某一層的構(gòu)造函數(shù)中如果調(diào)用了某個(gè)方法
* 并且這個(gè)方法被下層類重寫過,那么會(huì)直接執(zhí)行重寫之后的方法
* 所以執(zhí)行 Component::__construct厅篓,__construct 中調(diào)用 init()
* 會(huì)執(zhí)行 yii\base\Application 的 init
* 如果上級(jí)調(diào)用下級(jí)重寫的 靜態(tài)方法 時(shí)
* 要使用延時(shí)靜態(tài)綁定(上級(jí)靜態(tài)調(diào)用 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 實(shí)現(xiàn)調(diào)用 Container 依賴注入容器實(shí)例化對(duì)象
*/
$request = $this->getRequest();
//定義別名
Yii::setAlias('@webroot', dirname($request->getScriptFile()));
Yii::setAlias('@web', $request->getBaseUrl());
//調(diào)用 yii\base\Application::bootstrap 代碼
parent::bootstrap();
}
四羽氮、yii\base\Application::bootstrap 代碼太多或链,不展示源碼了,大致總結(jié)為
1乏苦、是否在配置文件中配置了 extensions 參數(shù)株扛,如果沒有配置尤筐,直接加載擴(kuò)展清單文件 @vendor/yiisoft/extensions.php汇荐,否則使用配置的 extensions。然后在 extensions 文件返回的數(shù)組中盆繁,可有含有 alias 和 bootstrap 參數(shù)掀淘,根據(jù) alias 中的參數(shù)定義別名,根據(jù) bootstrap 中的參數(shù)油昂,使用 createObject 實(shí)例化對(duì)象(創(chuàng)建并運(yùn)行各個(gè)擴(kuò)展聲明的 引導(dǎo)組件 )
2革娄、根據(jù)配置文件配置的 bootstrap 參數(shù),使用 createObject 實(shí)例化對(duì)象(創(chuàng)建并運(yùn)行各個(gè) 應(yīng)用組件 以及在應(yīng)用的 bootstrap 屬性中聲明的各個(gè) 模塊組件 )
3冕碟、注意:extensions 文件中配置的 bootstrap 和 配置文件中配置的 bootstrap拦惋,如果實(shí)現(xiàn)了 BootstrapInterface 接口,還會(huì)執(zhí)行實(shí)例化后的 bootstrap 方法
4安寺、注意:bootstrap 會(huì)直接將配置的類實(shí)例化厕妖,而不是在第一次使用的時(shí)候?qū)嵗詾榱诵阅芸紤] bootstrap 中的配置應(yīng)該盡量少挑庶,而且只配置一些全局使用的類
五言秸、yii\base\Application::run 代碼
public function run()
{
try {
$this->state = self::STATE_BEFORE_REQUEST;
/**
* trigger 觸發(fā)通知软能,將此事件通知給綁定到這個(gè)事件的觀察者,綁定事件的方法:
* 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 方法調(diào)用 urlManager 對(duì) url 進(jìn)行解析
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 {
/**
* 如果設(shè)置了 catchAll 變量, 那么所有請求都會(huì)跳轉(zhuǎn)到這里
* 示例:
* 假設(shè)網(wǎng)站維護(hù), 需要將網(wǎng)站重定向到一個(gè)設(shè)置好的頁面上
* 可以在配置文件中添加
* 'catchAll' => ['offline/index']
* 這樣, 所有的訪問都跳轉(zhuǎn)到 offline/index 頁面了
*/
$route = $this->catchAll[0];
$params = $this->catchAll;
unset($params[0]);
}
try {
Yii::debug("Route requested: '$route'", __METHOD__);
$this->requestedRoute = $route;
//根據(jù) route 訪問對(duì)應(yīng)的 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 代碼也不貼了,可以追進(jìn)去看抄沮,思路是
* 1跋核、如果 route 是空(直接通過域名訪問應(yīng)用 www.aaa.com)
* 使用配置中的 defaultRoute 屬性
* 2、route 不為空叛买,查看配置文件中是否有 controllerMap 的配置
* 直接使用配置創(chuàng)建
* controllerMap 配置如
* [
* 'controllerMap' => [
* // 用類名申明 "account" 控制器
* 'account' => 'app\controllers\UserController',
* // 用配置數(shù)組申明 "article" 控制器
* 'article' => [
* 'class' => 'app\controllers\PostController',
* 'enableCsrfValidation' => false,
* ]
* ]
* ]
*
* 3了罪、調(diào)用 yii/base/Module::getModule 查看 route 中是否有 module 存在
* 如果直接調(diào)用yii/base/Module::createController 方法
* 否則調(diào)用 yii/base/Module::createControllerByID
* 通過 createControllerByID 實(shí)例化的 Controller 類,必須繼承 yii\base\Controller
* createController 和 createControllerByID 都使用 Yii::createObject 實(shí)例化
*/
$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 這個(gè)很有意思
1泊藕、先看一下配置文件時(shí) modules 配置后的賦值過程
我們使用 modules 時(shí),需要在配置文件中配置 modules,比如
'modules' => [
'v1' => [
'class' => 'frontend\modules\v1\Module',
],
],
或者像 main-local.php 中那樣难礼,新建一個(gè) config,$config 中配置
$config['modules']['gii'] = [
'class' => 'yii\gii\Module',
];
這個(gè) modules 的屬性蛾茉,在 Application 及其父類中讼呢,都是不存在的
只有私有屬性 config) (不清楚的往上看谦炬,上邊有這塊代碼)
然后 Component::__construct(config) ,然后方法中執(zhí)行
if (!empty($config)) {
Yii::configure($this, $config);
}
再調(diào)用 yii\base\Component::setter 方法 (yii\base\Module::setModules)悦屏,將 $_modules 賦值
2、如果 module 套著 module键思,需要這么這么設(shè)置
'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 代碼也不貼了,可以追進(jìn)去看吼鳞,思路是
* 1看蚜、如果 action id 是空(訪問 www.aaa.com/controller)
* 使用 yii\base\Controller 中的 defaultAction 屬性
*
* 2、id 不為空赔桌,查看 Controller::actions 方法中是否有配置
* 如果有供炎,直接使用配置創(chuàng)建,actions 配置如
*
* public function actions()
* {
* return [
* 'error' => [
* 'class' => 'yii\web\ErrorAction',
* ],
* 'captcha' => [
* 'class' => 'yii\captcha\CaptchaAction',
* 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
* ],
* ];
* }
*
* 3疾党、利用反射(ReflectionMethod)查看調(diào)用方法是否存在音诫,是否是公共方法
* 如果是,返回 yii\base\InlineAction 的實(shí)例
*/
$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;
//調(diào)用所有加載模塊中的 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);
//調(diào)用所有加載模塊中的 afterAction 方法
foreach ($modules as $module) {
$result = $module->afterAction($action, $result);
}
}
if ($oldAction !== null) {
$this->action = $oldAction;
}
return $result;
}
最后雪位,附個(gè)圖竭钝,源自
http://www.yiichina.com/doc/guide/2.0/structure-applications