yii2啟動(dòng)流程分析

前言

本文主要分析Yii2應(yīng)用的啟動(dòng)、運(yùn)行的過程畏腕,主要包括以下三部分:入口腳本鸥拧、啟動(dòng)應(yīng)用、運(yùn)行應(yīng)用累榜。

入口腳本

<?php

// 定義全局變量
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');

// composer自動(dòng)加載代碼機(jī)制营勤,可參考 https://segmentfault.com/a/1190000010788354
require __DIR__ . '/../vendor/autoload.php';

// 1.引入工具類Yii
// 2.注冊(cè)自動(dòng)加載函數(shù)
// 3.生成依賴注入中使用到的容器
require __DIR__ . '/../vendor/yiisoft/yii2/Yii.php';

// 加載應(yīng)用配置文件
$config = require __DIR__ . '/../config/web.php';

//生成應(yīng)用實(shí)例并運(yùn)行
(new yii\web\Application($config))->run();

啟動(dòng)應(yīng)用

1.分析:new yii\web\Application($config)
主要就是執(zhí)行父類構(gòu)造函數(shù)(代碼位置:vendor\yiisoft\yii2\base\Application.php)

public function __construct($config = [])
    {
          // 將Yii::$app 保存為當(dāng)前對(duì)象
        Yii::$app = $this;
            
            // 見分析2
        static::setInstance($this);

          // 設(shè)置當(dāng)前應(yīng)用狀態(tài)
        $this->state = self::STATE_BEGIN;

        // 進(jìn)行一些預(yù)處理(根據(jù)$config配置應(yīng)用)
        // 詳細(xì)代碼位置:yii\base\Application::preInit(&$config) 見分析3
        $this->preInit($config);
                
            //注冊(cè)ErrorHandler灵嫌,這樣一旦拋出了異常或錯(cuò)誤葛作,就會(huì)由其負(fù)責(zé)處理
              //代碼位置:yii\base\Application::registerErrorHandler(&$config) 見分析4 
        $this->registerErrorHandler($config);
    
            // 根據(jù)$config配置Application
            // 然后執(zhí)行yii\web\Application::init()(實(shí)際上是執(zhí)行yii\base\Application::init())見分析5
        Component::__construct($config);
    }

2.分析:yii\base\Module::setInstance($instance)

    /**
     * Sets the currently requested instance of this module class.
     * @param Module|null $instance the currently requested instance of this module class.
     * If it is `null`, the instance of the calling class will be removed, if any.
     */
    public static function setInstance($instance)
    {
        if ($instance === null) {
            unset(Yii::$app->loadedModules[get_called_class()]);
        } else {
            // 將Application實(shí)例本身記錄為“已加載模塊”
            Yii::$app->loadedModules[get_class($instance)] = $instance;
        }
    }

3.分析:yii\base\Application::preInit(&$config)

        /**
     * Pre-initializes the application.
     * This method is called at the beginning of the application constructor.
     * It initializes several important application properties.
     * If you override this method, please make sure you call the parent implementation.
     * @param array $config the application configuration
     * @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing.
     */
    public function preInit(&$config)
    {
        // 1.判斷$config中是否有配置項(xiàng)‘id’寿羞,‘basePath’(必須有,否則拋異常)
        if (!isset($config['id'])) {
            throw new InvalidConfigException('The "id" configuration for the Application is required.');
        }
        if (isset($config['basePath'])) {
            $this->setBasePath($config['basePath']);
            unset($config['basePath']);
        } else {
            throw new InvalidConfigException('The "basePath" configuration for the Application is required.');
        }
                //  2.設(shè)置別名:@vendor,@runtime
        if (isset($config['vendorPath'])) {
            $this->setVendorPath($config['vendorPath']);
            unset($config['vendorPath']);
        } else {
            // set "@vendor"
            $this->getVendorPath();
        }
        
        if (isset($config['runtimePath'])) {
            $this->setRuntimePath($config['runtimePath']);
            unset($config['runtimePath']);
        } else {
            // set "@runtime"
            $this->getRuntimePath();
        }
                
        // 3.設(shè)置時(shí)間區(qū)域(timeZone)
        if (isset($config['timeZone'])) {
            $this->setTimeZone($config['timeZone']);
            unset($config['timeZone']);
        } elseif (!ini_get('date.timezone')) {
            $this->setTimeZone('UTC');
        }
                
        // 4.自定義配置容器(Yii::$container)的屬性(由這里我們知道可以自定義配置容器)
        if (isset($config['container'])) {
            $this->setContainer($config['container']);
            unset($config['container']);
        }
                
        // 5.合并核心組件配置到自定義組件配置:數(shù)組$config['components']
        //(核心組件有哪些參考:yii\web\Application::coreComponents())
        //(注意:這個(gè)方法中$config使用了引用赂蠢,所以合并$config['components']可以改變$config原來的值)
        // merge core components with custom components
        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'];
            }
        }
    }

4.分析:yii\base\Application::registerErrorHandler(&$config)

    /**
     * Registers the errorHandler component as a PHP error handler.
     * @param array $config application config
     */
    protected function registerErrorHandler(&$config)
    {
        if (YII_ENABLE_ERROR_HANDLER) {
            if (!isset($config['components']['errorHandler']['class'])) {
                echo "Error: no errorHandler component is configured.\n";
                exit(1);
            }
            $this->set('errorHandler', $config['components']['errorHandler']);
            unset($config['components']['errorHandler']);
            $this->getErrorHandler()->register();
        }
    }
    

    // 默認(rèn)config文件關(guān)于error handler的配置绪穆,這里的配置會(huì)合并到Y(jié)ii2本身對(duì)核心組件的定義中去(),核心組件的定義在yii\web\Application::coreComponents()
    'components' => [
            'errorHandler' => [
                'errorAction' => 'site/error',
            ],
    ],

        // yii\base\ErrorHandler::register()

        /**
     * Register this error handler.
     * @since 2.0.32 this will not do anything if the error handler was already registered
     */
    public function register()
    {
        if (!$this->_registered) {
            // 該選項(xiàng)設(shè)置是否將錯(cuò)誤信息作為輸出的一部分顯示到屏幕虱岂,
                  // 或者對(duì)用戶隱藏而不顯示玖院。
            ini_set('display_errors', false);
            // 當(dāng)拋出異常且沒有被catch的時(shí)候,由yii\base\ErrorHandler::handleException()處理
            set_exception_handler([$this, 'handleException']);
            if (defined('HHVM_VERSION')) {
                set_error_handler([$this, 'handleHhvmError']);
            } else {
                // 當(dāng)出現(xiàn)error的時(shí)候由yii\base\ErrorHandler::handleError()處理
                set_error_handler([$this, 'handleError']);
            }
            if ($this->memoryReserveSize > 0) {
                $this->_memoryReserve = str_repeat('x', $this->memoryReserveSize);
            }
            // 當(dāng)出現(xiàn)fatal error的時(shí)候由yii\base\ErrorHandler::handleFatalError()處理
            register_shutdown_function([$this, 'handleFatalError']);
            $this->_registered = true;
        }
    }
一個(gè)值得注意的地方:
在handleException()、handleError()第岖、handleFatalError()中會(huì)直接或間接調(diào)用yii\web\ErrorHandle::renderException(),而在這個(gè)函數(shù)里面难菌,有以下這一行代碼
$result = Yii::$app->runAction($this->errorAction);
回顧上面config文件中對(duì)errorAction這個(gè)屬性的定義,我們知道可以通過自定義一個(gè)action用于在異成馨粒或錯(cuò)誤時(shí)顯示自定義的出錯(cuò)頁面
例如yii2中默認(rèn)使用’site/error’這個(gè)action來處理

5.分析:Component::__construct($config)
(代碼實(shí)際位置:/vendor/yiisoft/yii2/base/BaseObject.php)

        /**
     * Constructor.
     *
     * The default implementation does two things:
     *
     * - Initializes the object with the given configuration `$config`.
     * - Call [[init()]].
     *
     * If this method is overridden in a child class, it is recommended that
     *
     * - the last parameter of the constructor is a configuration array, like `$config` here.
     * - call the parent implementation at the end of the constructor.
     *
     * @param array $config name-value pairs that will be used to initialize the object properties
     */
    public function __construct($config = [])
    {
        if (!empty($config)) {
            // 見下面的詳細(xì)分析
            Yii::configure($this, $config);
        }
        // 跳轉(zhuǎn)到y(tǒng)ii\base\Application::init()扔傅,見下面的分析
        $this->init();
    }

        
         /**
     * Configures an object with the initial property values.
     * @param object $object the object to be configured
     * @param array $properties the property initial values given in terms of name-value pairs.
     * @return object the object itself
     */
        //Yii::configure($object, $properties)分析
    //實(shí)際上就是為Application的屬性賦值
    //注意:這里會(huì)觸發(fā)一些魔術(shù)方法,間接調(diào)用了:
    //yii\web\Application::setHomeUrl()
    //yii\di\ServiceLocator::setComponents()(這個(gè)值得注意耍共,詳細(xì)參考本文后續(xù)“5.1分析”)
    //yii\base\Module::setModules()
    public static function configure($object, $properties)
    {
        foreach ($properties as $name => $value) {
            $object->$name = $value;
        }

        return $object;
    }
        
    /**
     * {@inheritdoc}
     */
        // yii\base\Application::init()分析
    public function init()
    {
        // 設(shè)置當(dāng)前應(yīng)用狀態(tài)
        $this->state = self::STATE_INIT;
        // 見下面的bootstrap()方法
        $this->bootstrap();
    }
    
         /**
     * Initializes extensions and executes bootstrap components.
     * This method is called by [[init()]] after the application has been fully configured.
     * If you override this method, make sure you also call the parent implementation.
     */
    protected function bootstrap()
    {
        if ($this->extensions === null) {
           // @vendor/yiisoft/extensions.php是一些關(guān)于第三方擴(kuò)展的配置烫饼,當(dāng)用composer require安裝第三擴(kuò)展的時(shí)候就會(huì)將新的擴(kuò)展的相關(guān)信息記錄到該文件,這樣我們就可以在代碼中調(diào)用了
            $file = Yii::getAlias('@vendor/yiisoft/extensions.php');
            $this->extensions = is_file($file) ? include $file : [];
        }
        foreach ($this->extensions as $extension) {
            if (!empty($extension['alias'])) {
                foreach ($extension['alias'] as $name => $path) {
                    Yii::setAlias($name, $path);
                }
            }
            // 進(jìn)行一些必要的預(yù)啟動(dòng)
            if (isset($extension['bootstrap'])) {
                $component = Yii::createObject($extension['bootstrap']);
                if ($component instanceof BootstrapInterface) {
                    Yii::debug('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);
                    $component->bootstrap($this);
                } else {
                    Yii::debug('Bootstrap with ' . get_class($component), __METHOD__);
                }
            }
        }
                // 預(yù)啟動(dòng)试读,通常會(huì)包括‘log’組件杠纵,‘debug’模塊和‘gii’模塊(參考配置文件)
        foreach ($this->bootstrap as $mixed) {
            $component = null;
            if ($mixed instanceof \Closure) {
                Yii::debug('Bootstrap with Closure', __METHOD__);
                if (!$component = call_user_func($mixed, $this)) {
                    continue;
                }
            } elseif (is_string($mixed)) {
                if ($this->has($mixed)) {
                    $component = $this->get($mixed);
                } elseif ($this->hasModule($mixed)) {
                    $component = $this->getModule($mixed);
                } elseif (strpos($mixed, '\\') === false) {
                    throw new InvalidConfigException("Unknown bootstrapping component ID: $mixed");
                }
            }

            if (!isset($component)) {
                $component = Yii::createObject($mixed);
            }

            if ($component instanceof BootstrapInterface) {
                Yii::debug('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);
                $component->bootstrap($this);
            } else {
                Yii::debug('Bootstrap with ' . get_class($component), __METHOD__);
            }
        }
    }

在完成了上面的流程之后,應(yīng)用就算啟動(dòng)成功了钩骇,可以開始運(yùn)行比藻,處理請(qǐng)求了。

運(yùn)行應(yīng)用

1.分析:yii\base\Application::run()
在上面的構(gòu)造函數(shù)執(zhí)行完后倘屹,開始運(yùn)行應(yīng)用银亲。即下面這行代碼的run()部分
(new yii\web\Application($config))->run();//(實(shí)際上執(zhí)行的是yii\base\Application::run())

 /**
     * Runs the application.
     * This is the main entrance of an application.
     * @return int the exit status (0 means normal, non-zero values mean abnormal)
     */
    public function run()
    {
        try {
            $this->state = self::STATE_BEFORE_REQUEST;
            // 觸發(fā)事件’beforeRequest’,依次執(zhí)行該事件的handler,
            $this->trigger(self::EVENT_BEFORE_REQUEST);

            $this->state = self::STATE_HANDLING_REQUEST;
            //處理請(qǐng)求纽匙,這里的返回值是yii\web\Response實(shí)例
            //handleRequest(),詳細(xì)參考本文后續(xù)“分析2”
            $response = $this->handleRequest($this->getRequest());

            $this->state = self::STATE_AFTER_REQUEST;
            // 觸發(fā)事件’afterRequest’,依次執(zhí)行該事件的handler
            $this->trigger(self::EVENT_AFTER_REQUEST);
        
            $this->state = self::STATE_SENDING_RESPONSE;
            // 發(fā)送響應(yīng),詳細(xì)參考本文后續(xù)“分析3”
            $response->send();

            $this->state = self::STATE_END;

            return $response->exitStatus;
        } catch (ExitException $e) {
            // 結(jié)束運(yùn)行
            $this->end($e->statusCode, isset($response) ? $response : null);
            return $e->statusCode;
        }
    }

2.分析: yii\web\Application::handleRequest()

 /**
     * Handles the specified request.
     * @param Request $request the request to be handled
     * @return Response the resulting response
     * @throws NotFoundHttpException if the requested route is invalid
     */
    public function handleRequest($request)
    {
        if (empty($this->catchAll)) {
            try {
                // 從請(qǐng)求中解析路由和參數(shù)务蝠,這里會(huì)調(diào)用urlManager組件來處理
                list($route, $params) = $request->resolve();
            } catch (UrlNormalizerRedirectException $e) {
                $url = $e->url;
                if (is_array($url)) {
                    if (isset($url[0])) {
                        // ensure the route is absolute
                        $url[0] = '/' . ltrim($url[0], '/');
                    }
                    $url += $request->getQueryParams();
                }
                                 // 當(dāng)解析請(qǐng)求出現(xiàn)異常時(shí)進(jìn)行重定向
                return $this->getResponse()->redirect(Url::to($url, $e->scheme), $e->statusCode);
            }
        } else {
            // ’catchAll’參數(shù)可以在配置文件中自定義,可用于在項(xiàng)目需要臨時(shí)下線維護(hù)時(shí)給出一個(gè)統(tǒng)一的訪問路由
            $route = $this->catchAll[0];
            $params = $this->catchAll;
            unset($params[0]);
        }
        try {
            Yii::debug("Route requested: '$route'", __METHOD__);
            // 記錄下當(dāng)前請(qǐng)求的route
            $this->requestedRoute = $route;
            // 執(zhí)行路由相對(duì)應(yīng)的action(yii\base\Module::runAction()詳細(xì)參考本文后續(xù)“分析3”)
            $result = $this->runAction($route, $params);
            if ($result instanceof Response) {
                return $result;
            }
                        // 如果action的返回結(jié)果不是Response的實(shí)例烛缔,則將結(jié)果封裝到Response實(shí)例的data屬性中
            $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);
        }
    }

3.分析: yii\base\Module::runAction()

public function runAction($route, $params = [])
{
    //根據(jù)路由創(chuàng)建Controller實(shí)例(詳細(xì)參考本文后續(xù)“分析4”)
    $parts = $this->createController($route);
    if (is_array($parts)) {
        /* @var $controller Controller */
        // 解析出控制器和方法
        list($controller, $actionID) = $parts;
        $oldController = Yii::$app->controller;
        // 設(shè)置當(dāng)前的Controller實(shí)例
        Yii::$app->controller = $controller;
        // 執(zhí)行action(yii\base\Controller::runAction()詳細(xì)參考本文后續(xù)“分析5”)
        $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) . '".');
}

4.分析: yii\base\Module::createController()

         /**
     * Creates a controller instance based on the given route.
     *
     * The route should be relative to this module. The method implements the following algorithm
     * to resolve the given route:
     *
     * 1. If the route is empty, use [[defaultRoute]];
     * 2. If the first segment of the route is found in [[controllerMap]], create a controller
     *    based on the corresponding configuration found in [[controllerMap]];
     * 3. If the first segment of the route is a valid module ID as declared in [[modules]],
     *    call the module's `createController()` with the rest part of the route;
     * 4. The given route is in the format of `abc/def/xyz`. Try either `abc\DefController`
     *    or `abc\def\XyzController` class within the [[controllerNamespace|controller namespace]].
     *
     * If any of the above steps resolves into a controller, it is returned together with the rest
     * part of the route which will be treated as the action ID. Otherwise, `false` will be returned.
     *
     * @param string $route the route consisting of module, controller and action IDs.
     * @return array|bool If the controller is created successfully, it will be returned together
     * with the requested action ID. Otherwise `false` will be returned.
     * @throws InvalidConfigException if the controller class and its file do not match.
     */
    public function createController($route)
    {
         // 如果route為空則設(shè)置route為默認(rèn)路由馏段,這個(gè)可以在配置文件中自定義
        if ($route === '') {
            $route = $this->defaultRoute;
        }

        // double slashes or leading/ending slashes may cause substr problem
        $route = trim($route, '/');
        if (strpos($route, '//') !== false) {
            return false;
        }

        if (strpos($route, '/') !== false) {
            list($id, $route) = explode('/', $route, 2);
        } else {
            $id = $route;
            $route = '';
        }
            
        // 優(yōu)先使用controllerMap,controllerMap可以如下
        // module and controller map take precedence
        if (isset($this->controllerMap[$id])) {
            $controller = Yii::createObject($this->controllerMap[$id], [$id, $this]);
            return [$controller, $route];
        }
        // 先判斷是否存在相應(yīng)的模塊
        $module = $this->getModule($id);
        if ($module !== null) {
            // 當(dāng)存在模塊時(shí)践瓷,進(jìn)行遞歸
            return $module->createController($route);
        }

        if (($pos = strrpos($route, '/')) !== false) {
            $id .= '/' . substr($route, 0, $pos);
            $route = substr($route, $pos + 1);
        }
                // 最終找到Controller的id
        $controller = $this->createControllerByID($id);
        if ($controller === null && $route !== '') {
            // 詳細(xì)見下面的代碼分析
            $controller = $this->createControllerByID($id . '/' . $route);
            $route = '';
        }
                 // 返回Controller實(shí)例和剩下的路由信息
        return $controller === null ? false : [$controller, $route];
    }
        
        /**
     * Creates a controller based on the given controller ID.
     *
     * The controller ID is relative to this module. The controller class
     * should be namespaced under [[controllerNamespace]].
     *
     * Note that this method does not check [[modules]] or [[controllerMap]].
     *
     * @param string $id the controller ID.
     * @return Controller|null the newly created controller instance, or `null` if the controller ID is invalid.
     * @throws InvalidConfigException if the controller class and its file name do not match.
     * This exception is only thrown when in debug mode.
     */
    public function createControllerByID($id)
    {
        $pos = strrpos($id, '/');
        if ($pos === false) {
            $prefix = '';
            $className = $id;
        } else {
            $prefix = substr($id, 0, $pos + 1);
            $className = substr($id, $pos + 1);
        }

        if ($this->isIncorrectClassNameOrPrefix($className, $prefix)) {
            return null;
        }

        $className = preg_replace_callback('%-([a-z0-9_])%i', function ($matches) {
                return ucfirst($matches[1]);
            }, ucfirst($className)) . 'Controller';
        $className = ltrim($this->controllerNamespace . '\\' . str_replace('/', '\\', $prefix) . $className, '\\');
        if (strpos($className, '-') !== false || !class_exists($className)) {
            return null;
        }

        if (is_subclass_of($className, 'yii\base\Controller')) {
            // 通過依賴注入容器獲得Controller實(shí)例
            $controller = Yii::createObject($className, [$id, $this]);
            return get_class($controller) === $className ? $controller : null;
        } elseif (YII_DEBUG) {
            throw new InvalidConfigException('Controller class must extend from \\yii\\base\\Controller.');
        }

        return null;
    }

5.分析:yii\base\Controller::runAction()

/**
     * Runs an action within this controller with the specified action ID and parameters.
     * If the action ID is empty, the method will use [[defaultAction]].
     * @param string $id the ID of the action to be executed.
     * @param array $params the parameters (name-value pairs) to be passed to the action.
     * @return mixed the result of the action.
     * @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully.
     * @see createAction()
     */
    public function runAction($id, $params = [])
    {
        // 創(chuàng)建action實(shí)例院喜,詳細(xì)見下面的代碼
        $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;

        // 返回的modules包括該controller當(dāng)前所在的module,以及該module的所有祖先module(遞歸直至沒有祖先module)
            // 然后從最初的祖先module開始晕翠,依次執(zhí)行“模塊級(jí)”的beforeAction()
            // 如果有beforeAction()沒有返回true喷舀, 那么會(huì)中斷后續(xù)的執(zhí)行
        // call beforeAction on modules
        foreach ($this->getModules() as $module) {
            if ($module->beforeAction($action)) {
                array_unshift($modules, $module);
            } else {
                $runAction = false;
                break;
            }
        }

        $result = null;
                // 執(zhí)行當(dāng)前控制器的beforeAction,通過后再最終執(zhí)行action
          //(如果前面“模塊級(jí)beforeAction”沒有全部返回true,則這里不會(huì)執(zhí)行)
        if ($runAction && $this->beforeAction($action)) {
            // run the action
            //代碼位置:yii\base\InlineAction::runWithParams()詳細(xì)參考本文后續(xù)
            $result = $action->runWithParams($params);
                        // 執(zhí)行當(dāng)前Controller的afterAction
            $result = $this->afterAction($action, $result);

            // call afterAction on modules
            // 從當(dāng)前模塊開始元咙,執(zhí)行afterAction梯影,直至所有祖先的afterAction
            foreach ($modules as $module) {
                /* @var $module Module */
                $result = $module->afterAction($action, $result);
            }
        }

        if ($oldAction !== null) {
            $this->action = $oldAction;
        }
                // 如果有beforeAction沒有通過,那么會(huì)返回null
        return $result;
    }
        
        /**
     * Creates an action based on the given action ID.
     * The method first checks if the action ID has been declared in [[actions()]]. If so,
     * it will use the configuration declared there to create the action object.
     * If not, it will look for a controller method whose name is in the format of `actionXyz`
     * where `xyz` is the action ID. If found, an [[InlineAction]] representing that
     * method will be created and returned.
     * @param string $id the action ID.
     * @return Action|null the newly created action instance. Null if the ID doesn't resolve into any action.
     */
        // 根據(jù)給定的動(dòng)作ID創(chuàng)建一個(gè)動(dòng)作庶香。該方法首先檢查動(dòng)作ID是否已在[[actions()]]中聲明甲棍。如果是這樣,它將使用在那里聲明的配置來創(chuàng)建操作對(duì)象赶掖。如果不是感猛,它將尋找名稱為actionXyz格式的控制器方法,其中xyz是動(dòng)作ID奢赂。如果找到陪白,將創(chuàng)建并返回表示該方法的[[InlineAction]]。
    public function createAction($id)
    {
        if ($id === '') {
            $id = $this->defaultAction;
        }

        $actionMap = $this->actions();
        if (isset($actionMap[$id])) {
            return Yii::createObject($actionMap[$id], [$id, $this]);
        }

        if (preg_match('/^(?:[a-z0-9_]+-)*[a-z0-9_]+$/', $id)) {
            $methodName = 'action' . str_replace(' ', '', ucwords(str_replace('-', ' ', $id)));
            if (method_exists($this, $methodName)) {
                $method = new \ReflectionMethod($this, $methodName);
                if ($method->isPublic() && $method->getName() === $methodName) {
                    // InlineAction封裝了將要執(zhí)行的action的相關(guān)信息膳灶,該類繼承自yii\base\Action
                    return new InlineAction($id, $this, $methodName);
                }
            }
        }

        return null;
    }


6.分析: yii\web\Response::send()

         /**
     * Sends the response to the client.
     */
    public function send()
    {
        if ($this->isSent) {
            return;
        }
        // 觸發(fā)事件
        $this->trigger(self::EVENT_BEFORE_SEND);
        // 預(yù)處理 
        $this->prepare();
        // 觸發(fā)事件
        $this->trigger(self::EVENT_AFTER_PREPARE);
        // 發(fā)送http響應(yīng)的頭部咱士,詳見下面的代碼
        $this->sendHeaders();
        // 發(fā)送http響應(yīng)的主體,詳見下面的代碼
        $this->sendContent();
        // 觸發(fā)事件
        $this->trigger(self::EVENT_AFTER_SEND);
        $this->isSent = true;
    }

請(qǐng)求生命周期

請(qǐng)求生命周期
  1. 用戶向入口腳本 web/index.php 發(fā)起請(qǐng)求轧钓。
  2. 入口腳本加載應(yīng)用配置并創(chuàng)建一個(gè)應(yīng)用 實(shí)例去處理請(qǐng)求序厉。
  3. 應(yīng)用通過請(qǐng)求組件解析請(qǐng)求的 路由
  4. 應(yīng)用創(chuàng)建一個(gè)控制器實(shí)例去處理請(qǐng)求毕箍。
  5. 控制器創(chuàng)建一個(gè)動(dòng)作實(shí)例并針對(duì)操作執(zhí)行過濾器弛房。
  6. 如果任何一個(gè)過濾器返回失敗,則動(dòng)作取消而柑。
  7. 如果所有過濾器都通過文捶,動(dòng)作將被執(zhí)行。
  8. 動(dòng)作會(huì)加載一個(gè)數(shù)據(jù)模型媒咳,或許是來自數(shù)據(jù)庫粹排。
  9. 動(dòng)作會(huì)渲染一個(gè)視圖,把數(shù)據(jù)模型提供給它涩澡。
  10. 渲染結(jié)果返回給響應(yīng)組件顽耳。
  11. 響應(yīng)組件發(fā)送渲染結(jié)果給用戶瀏覽器。

靜態(tài)結(jié)構(gòu)

應(yīng)用靜態(tài)結(jié)構(gòu)

每個(gè)應(yīng)用都有一個(gè)入口腳本 web/index.php筏养,這是整個(gè)應(yīng)用中唯一可以訪問的 PHP 腳本斧抱。 入口腳本接受一個(gè) Web 請(qǐng)求并創(chuàng)建應(yīng)用實(shí)例去處理它。 應(yīng)用在它的組件輔助下解析請(qǐng)求渐溶, 并分派請(qǐng)求至 MVC 元素辉浦。視圖使用小部件 去創(chuàng)建復(fù)雜和動(dòng)態(tài)的用戶界面。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末茎辐,一起剝皮案震驚了整個(gè)濱河市宪郊,隨后出現(xiàn)的幾起案子掂恕,更是在濱河造成了極大的恐慌,老刑警劉巖弛槐,帶你破解...
    沈念sama閱讀 221,331評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件懊亡,死亡現(xiàn)場離奇詭異,居然都是意外死亡乎串,警方通過查閱死者的電腦和手機(jī)店枣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叹誉,“玉大人鸯两,你說我怎么就攤上這事〕せ恚” “怎么了钧唐?”我有些...
    開封第一講書人閱讀 167,755評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長匠襟。 經(jīng)常有香客問我钝侠,道長,這世上最難降的妖魔是什么酸舍? 我笑而不...
    開封第一講書人閱讀 59,528評(píng)論 1 296
  • 正文 為了忘掉前任帅韧,我火速辦了婚禮,結(jié)果婚禮上父腕,老公的妹妹穿的比我還像新娘弱匪。我一直安慰自己青瀑,他們只是感情好璧亮,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,526評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著斥难,像睡著了一般枝嘶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上哑诊,一...
    開封第一講書人閱讀 52,166評(píng)論 1 308
  • 那天群扶,我揣著相機(jī)與錄音,去河邊找鬼镀裤。 笑死竞阐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的暑劝。 我是一名探鬼主播骆莹,決...
    沈念sama閱讀 40,768評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼担猛!你這毒婦竟也來了幕垦?” 一聲冷哼從身側(cè)響起丢氢,我...
    開封第一講書人閱讀 39,664評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎先改,沒想到半個(gè)月后疚察,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,205評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仇奶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,290評(píng)論 3 340
  • 正文 我和宋清朗相戀三年貌嫡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片该溯。...
    茶點(diǎn)故事閱讀 40,435評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡衅枫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出朗伶,到底是詐尸還是另有隱情弦撩,我是刑警寧澤,帶...
    沈念sama閱讀 36,126評(píng)論 5 349
  • 正文 年R本政府宣布论皆,位于F島的核電站益楼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏点晴。R本人自食惡果不足惜感凤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,804評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望粒督。 院中可真熱鬧陪竿,春花似錦、人聲如沸屠橄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,276評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锐墙。三九已至礁哄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間溪北,已是汗流浹背桐绒。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留之拨,地道東北人茉继。 一個(gè)月前我還...
    沈念sama閱讀 48,818評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像蚀乔,于是被迫代替她去往敵國和親烁竭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,442評(píng)論 2 359

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