yii2 入門 源碼分析-入口文件執(zhí)行流程

最近在看 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娃圆,配置完以后 returnconfig,$config 中配置

$config['modules']['gii'] = [
    'class' => 'yii\gii\Module',
];

這個(gè) modules 的屬性蛾茉,在 Application 及其父類中讼呢,都是不存在的
只有私有屬性 _modules,存在于 yii\base\Module 類中 當(dāng) new yii\web\Application 執(zhí)行 yii\base\Application::__construct 方法時(shí) 方法中執(zhí)行了 Component::__construct(config) (不清楚的往上看谦炬,上邊有這塊代碼)
然后 Component::__construct(config) 實(shí)際執(zhí)行的是 BaseObject::__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

流程圖
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蜓氨,更是在濱河造成了極大的恐慌聋袋,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件穴吹,死亡現(xiàn)場離奇詭異幽勒,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)港令,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門啥容,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人顷霹,你說我怎么就攤上這事咪惠。” “怎么了淋淀?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵遥昧,是天一觀的道長。 經(jīng)常有香客問我朵纷,道長炭臭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任袍辞,我火速辦了婚禮鞋仍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘搅吁。我一直安慰自己威创,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布谎懦。 她就那樣靜靜地躺著肚豺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪党瓮。 梳的紋絲不亂的頭發(fā)上详炬,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音寞奸,去河邊找鬼。 笑死在跳,一個(gè)胖子當(dāng)著我的面吹牛枪萄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播猫妙,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼瓷翻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起齐帚,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤妒牙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后对妄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體湘今,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年剪菱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了摩瞎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡孝常,死狀恐怖旗们,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情构灸,我是刑警寧澤上渴,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站喜颁,受9級(jí)特大地震影響驰贷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜洛巢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一括袒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧稿茉,春花似錦锹锰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至渺蒿,卻和暖如春痢士,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背茂装。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國打工怠蹂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人少态。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓城侧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親彼妻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嫌佑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容