yii2框架源碼分析系列(5)之Application

回顧

之前聊入口的時(shí)候聊到了Yii::createObject(),然后又跟著這條線解析了下ContainerService 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ù)介紹

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 2017年11月15日,周三,晴天。 #父母是孩子最好的榜樣# 孩子第一個(gè)30天目標(biāo):早21點(diǎn)睡早七點(diǎn)睡 媽媽第一...
    靈動(dòng)宮主曹蕓熙閱讀 290評(píng)論 0 0
  • 我設(shè)計(jì)的萌寶--我兒子 五一節(jié)旅行時(shí)酒店送的小雞,兒子喜歡得緊,可惜弄丟了。顏色不會(huì)搭配,所以就選了比較保險(xiǎn)的三原...
    素心cathy閱讀 162評(píng)論 2 0
  • “兒童自身隱藏著一種生氣勃勃的生命秘密,他們與成人的生活緊密相關(guān)。” 當(dāng)我被那些六七歲的兒童多次稱呼阿姨,當(dāng)我從最...
    Irisreader閱讀 527評(píng)論 0 0
  • 明天的你,是否會(huì)在小巷里 和我相遇? 明天的雨,是否會(huì)在這秋季 淅淅瀝瀝? 流逝的青春歲月,在日記的背面 寫(xiě)下了你...
    鱘余閱讀 162評(píng)論 1 4

友情鏈接更多精彩內(nèi)容