Yii2 源碼分析 - 入口文件執行流程

以 yii 2.0.14 高級版的 frontend 爲例,從 frontend/web/index.php 開始php

//引用 yii2 composer 的 autoload,調用 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();

入口文件看着就這麼幾行,簡單的很,那他是怎麼經過這幾行來運行應用的呢?先看 Yii.php 內的邏輯html

/**
 * Yii::autoload 內執行過程
 * 一、先查看類是否在 Yii::$classMap 中存在,存在直接調用 getAlias 生成類文件物理地址
 * 二、若是 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 配置實現事件綁定web

1、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 內代碼
	 * 一、調用 $this->set('errorHandler', $config['components']['errorHandler']) 
     * 將 errorHandler 配置放到 ServiceLocator (_definitions 數組中,這時還沒實例化)
	 * 二、調用 $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);
}

2、yii\base\Application::init 代碼

public function init()
{
	$this->state = self::STATE_INIT;
	$this->bootstrap();
}

3、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();
}

4、yii\base\Application::bootstrap 代碼太多,不展現源碼了,大體總結爲

一、是否在配置文件中配置了 extensions 參數,若是沒有配置,直接加載擴展清單文件 @vendor/yiisoft/extensions.php,不然使用配置的 extensions。而後在 extensions 文件返回的數組中,可有含有 alias 和 bootstrap 參數,根據 alias 中的參數定義別名,根據 bootstrap 中的參數,使用 createObject 實例化對象(建立並運行各個擴展聲明的 引導組件 )
二、根據配置文件配置的 bootstrap 參數,使用 createObject 實例化對象(建立並運行各個 應用組件 以及在應用的 bootstrap 屬性中聲明的各個 模塊組件 )
三、注意:extensions 文件中配置的 bootstrap 和 配置文件中配置的 bootstrap,若是實現了 BootstrapInterface 接口,還會執行實例化後的 bootstrap 方法
四、注意:bootstrap 會直接將配置的類實例化,而不是在第一次使用的時候實例化,因此爲了性能考慮 bootstrap 中的配置應該儘可能少,並且只配置一些全局使用的類bootstrap

5、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;
    }
}

6、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);
    }
}

7、yii\base\Module::runAction 代碼

public function runAction($route, $params = [])
{
	/**
     * yii\base\Module::createController 代碼也不貼了,能夠追進去看,思路是
     * 一、若是 route 是空(直接經過域名訪問應用 www.aaa.com)
     * 使用配置中的 defaultRoute 屬性
     * 二、route 不爲空,查看配置文件中是否有 controllerMap 的配置
     * 直接使用配置建立
     * controllerMap 配置如
     * [
     *     'controllerMap' => [
     *         // 用類名申明 "account" 控制器
     *         'account' => 'app\controllers\UserController',
     *         // 用配置數組申明 "article" 控制器
     *         'article' => [
     *             'class' => 'app\controllers\PostController',
     *             'enableCsrfValidation' => false,
     *         ]
     *     ]
     * ]
     * 
     * 三、調用 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) . '".');
}

8、說明一下 yii/base/Module::getModule 這個頗有意思

一、先看一下配置文件時 modules 配置後的賦值過程
咱們使用 modules 時,須要在配置文件中配置 modules,好比swift

'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) ,而後方法中執行yii2

if (!empty($config)) {
    Yii::configure($this, $config);
}

再調用 yii\base\Component::setter 方法 (yii\base\Module::setModules),將 $_modules 賦值
二、若是 module 套着 module,須要這麼這麼設置session

'modules' => [
    'v1' => [
        'class' 		=> 'frontend\modules\v1\Module',
        'modules'	=> [
        	'v2'	=> 'frontend\modules\v2\Module'
        ],
    ],
],

9、yii\base\Controller::runAction 代碼

public function runAction($id, $params = [])
{
	/**
     * yii\base\Controller::createAction 代碼也不貼了,能夠追進去看,思路是
     * 一、若是 action id 是空(訪問 www.aaa.com/controller)
     * 使用 yii\base\Controller 中的 defaultAction 屬性
     * 
     * 二、id 不爲空,查看 Controller::actions 方法中是否有配置
     * 若是有,直接使用配置建立,actions 配置如
     * 
     * public function actions()
     * {
     *     return [
     *         'error' => [
     *             'class' => 'yii\web\ErrorAction',
     *         ],
     *         'captcha' => [
     *             'class' => 'yii\captcha\CaptchaAction',
     *             'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
     *         ],
     *     ];
     * }
     * 
     * 三、利用反射(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-applicationsapp

application-lifecycle.png

 
 
G
M
T
 
 
Detect languageAfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBengaliBosnianBulgarianCatalanCebuanoChichewaChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDutchEnglishEsperantoEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekGujaratiHaitian CreoleHausaHebrewHindiHmongHungarianIcelandicIgboIndonesianIrishItalianJapaneseJavaneseKannadaKazakhKhmerKoreanLaoLatinLatvianLithuanianMacedonianMalagasyMalayMalayalamMalteseMaoriMarathiMongolianMyanmar (Burmese)NepaliNorwegianPersianPolishPortuguesePunjabiRomanianRussianSerbianSesothoSinhalaSlovakSlovenianSomaliSpanishSundaneseSwahiliSwedishTajikTamilTeluguThaiTurkishUkrainianUrduUzbekVietnameseWelshYiddishYorubaZulu
 
AfrikaansAlbanianArabicArmenianAzerbaijaniBasqueBelarusianBengaliBosnianBulgarianCatalanCebuanoChichewaChinese (Simplified)Chinese (Traditional)CroatianCzechDanishDutchEnglishEsperantoEstonianFilipinoFinnishFrenchGalicianGeorgianGermanGreekGujaratiHaitian CreoleHausaHebrewHindiHmongHungarianIcelandicIgboIndonesianIrishItalianJapaneseJavaneseKannadaKazakhKhmerKoreanLaoLatinLatvianLithuanianMacedonianMalagasyMalayMalayalamMalteseMaoriMarathiMongolianMyanmar (Burmese)NepaliNorwegianPersianPolishPortuguesePunjabiRomanianRussianSerbianSesothoSinhalaSlovakSlovenianSomaliSpanishSundaneseSwahiliSwedishTajikTamilTeluguThaiTurkishUkrainianUrduUzbekVietnameseWelshYiddishYorubaZulu
 
 
 
 
 
 
 
 
 
Text-to-speech function is limited to 200 characters
 
 
Options : History : Feedback : Donate Close
相關文章
相關標籤/搜索