回顧
之前聊入口的時(shí)候聊到了Yii::createObject(),然后又跟著這條線解析了下Container和Service Locator,有點(diǎn)偏離了,今天繼續(xù)從入口分析下yii2的Application
Application
入口index.php的最后一行代碼(new yii\web\Application($config))->run()直接新建一個(gè)Application實(shí)例并調(diào)用對(duì)應(yīng)的run()方法,這里就正式進(jìn)入到了框架的應(yīng)用層面,來(lái)看下這個(gè)類(lèi)的構(gòu)造方法
public function __construct($config = [])
{
// 初始化BaseYii類(lèi)中的$app屬性,賦值$this
// 使得全局可訪問(wèn)應(yīng)用實(shí)例
Yii::$app = $this;
// 調(diào)用Module類(lèi)的方法
static::setInstance($this);
// 設(shè)置啟動(dòng)狀態(tài)
$this->state = self::STATE_BEGIN;
// 一些初始化工作
$this->preInit($config);
// 注冊(cè)錯(cuò)誤處理
$this->registerErrorHandler($config);
// 調(diào)用組件構(gòu)造函數(shù)
Component::__construct($config);
}
看下初始化都干了些啥
public function preInit(&$config)
{
// 應(yīng)用ID都沒(méi)有,死去吧
if (!isset($config['id'])) {
throw new InvalidConfigException('The "id" configuration for the Application is required.');
}
// 設(shè)置項(xiàng)目根路徑
// 會(huì)使用別名app代替
if (isset($config['basePath'])) {
$this->setBasePath($config['basePath']);
unset($config['basePath']);
} else {
throw new InvalidConfigException('The "basePath" configuration for the Application is required.');
}
// 設(shè)置vendor路徑
// 會(huì)使用別名vendor
if (isset($config['vendorPath'])) {
$this->setVendorPath($config['vendorPath']);
unset($config['vendorPath']);
} else {
// 獲取默認(rèn)vendor路徑,并設(shè)置別名vendor
$this->getVendorPath();
}
// 同上,設(shè)置運(yùn)行時(shí)路徑
if (isset($config['runtimePath'])) {
$this->setRuntimePath($config['runtimePath']);
unset($config['runtimePath']);
} else {
// set "@runtime"
$this->getRuntimePath();
}
// 同上,設(shè)置時(shí)區(qū)
if (isset($config['timeZone'])) {
$this->setTimeZone($config['timeZone']);
unset($config['timeZone']);
} elseif (!ini_get('date.timezone')) {
$this->setTimeZone('UTC');
}
// 初始化容器,可以設(shè)置一些依賴配置
if (isset($config['container'])) {
$this->setContainer($config['container']);
unset($config['container']);
}
// 配置一些核心的組件
// 這些組件你可以不適用框架默認(rèn)的,但是必須得有
// coreComponents()方法中包含了核心組件
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'])) {
// 這是配置了一些參數(shù),增加對(duì)應(yīng)的組件類(lèi)
$config['components'][$id]['class'] = $component['class'];
}
}
}
Application類(lèi)主要是根據(jù)傳入的配置做一些路徑初始化,設(shè)置實(shí)例和狀態(tài)以及組件配置的檢查和補(bǔ)充,再看看Component類(lèi)的構(gòu)造函數(shù)
// Component是繼承父類(lèi)的構(gòu)造函數(shù)的,所以這里調(diào)用的是框架的基類(lèi)BaseObject的構(gòu)造函數(shù)
public function __construct($config = [])
{
if (!empty($config)) {
// 調(diào)用BaseYii的configure方法
Yii::configure($this, $config);
}
// 初始化
$this->init();
}
Yii::configure()方法邏輯很簡(jiǎn)單,就是遍歷config設(shè)置類(lèi)屬性,這里的config就是入口傳進(jìn)來(lái)并經(jīng)過(guò)Application類(lèi)處理過(guò)的配置,yii2中通過(guò)魔術(shù)方法__set()來(lái)針對(duì)不同的配置來(lái)進(jìn)行不同的動(dòng)作,根據(jù)這條線,可以知道設(shè)置屬性的主角是Component類(lèi)的__set()方法,來(lái)看看它干了些什么
public function __set($name, $value)
{
$setter = 'set' . $name;
// 如果有對(duì)應(yīng)的setName方法直接調(diào)用
// 如config中有一個(gè)組件配置['components' => []]
// 此時(shí)name=components
// 調(diào)用setComponents方法,這個(gè)方法不陌生吧,正是之前介紹的Service Locator類(lèi)中的方法
// Application 繼承 Module 繼承Service Locator 繼承 Component 繼承BaseObject
if (method_exists($this, $setter)) {
$this->$setter($value);
return;
} elseif (strncmp($name, 'on ', 3) === 0) {
// 格式 on event,綁定事件處理
// Component類(lèi)的一個(gè)巨大特性就是支持行為事件,這里先埋著,后面挖出來(lái)介紹
$this->on(trim(substr($name, 3)), $value);
return;
} elseif (strncmp($name, 'as ', 3) === 0) {
// 格式 as behavior,綁定行為
$name = trim(substr($name, 3));
$this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value));
return;
}
// 確保所有行為已綁定,這個(gè)后面單獨(dú)講Component的時(shí)候再介紹
$this->ensureBehaviors();
foreach ($this->_behaviors as $behavior) {
if ($behavior->canSetProperty($name)) {
// 設(shè)置行為屬性
$behavior->$name = $value;
return;
}
}
if (method_exists($this, 'get' . $name)) {
throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
}
throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
}
這里說(shuō)一下,到這里config中的components會(huì)注冊(cè)到服務(wù)定位器中,如
$config = [
'components' => [
'user' => [
'identityClass' => 'app\models\User',
'enableAutoLogin' => true,
],
]
]
注冊(cè)完之后你就可以在程序中通過(guò)Yii::$app->user來(lái)獲取到user組件的實(shí)例了,因?yàn)檫@里會(huì)調(diào)用Service Locator類(lèi)的__get()方法,繼而調(diào)用get()方法,繼而調(diào)用Yii::createObject()方法使用Container容器實(shí)例化出來(lái)。BaseObject類(lèi)執(zhí)行完配置之后會(huì)繼續(xù)調(diào)用Application類(lèi)的init()方法
public function init()
{
// 設(shè)置狀態(tài)為初始化
$this->state = self::STATE_INIT;
// 調(diào)用bootstrap方法,初始化yii2擴(kuò)展組件
$this->bootstrap();
}
init()方法就干了兩件事,首先設(shè)置應(yīng)用執(zhí)行狀態(tài)為初始化,然后調(diào)用本來(lái)的bootstrap()方法,這里不再詳細(xì)介紹。Application類(lèi)的構(gòu)造函數(shù)執(zhí)行完畢了,現(xiàn)在該執(zhí)行run()方法了
// 正如函數(shù)名所描述,前置的初始化和配置以及加載擴(kuò)展都弄完了,萬(wàn)事俱備,開(kāi)始執(zhí)行
public function run()
{
try {
// 執(zhí)行請(qǐng)求開(kāi)始前的事件
$this->state = self::STATE_BEFORE_REQUEST;
$this->trigger(self::EVENT_BEFORE_REQUEST);
// 執(zhí)行請(qǐng)求
$this->state = self::STATE_HANDLING_REQUEST;
$response = $this->handleRequest($this->getRequest());
// 執(zhí)行請(qǐng)求完畢后的事件
$this->state = self::STATE_AFTER_REQUEST;
$this->trigger(self::EVENT_AFTER_REQUEST);
// 執(zhí)行返回
$this->state = self::STATE_SENDING_RESPONSE;
$response->send();
// 執(zhí)行結(jié)束
$this->state = self::STATE_END;
return $response->exitStatus;
} catch (ExitException $e) {
$this->end($e->statusCode, isset($response) ? $response : null);
return $e->statusCode;
}
}
隨著run()執(zhí)行完畢,應(yīng)用也就執(zhí)行完畢了,yii2的整個(gè)生命周期就結(jié)束了,至于請(qǐng)求是如何被執(zhí)行的,怎么解析url,怎么定位到controller,又怎么定位到action等等,這個(gè)后面的篇幅再繼續(xù)介紹