YII2源碼分析(1) --- 基本流程分析

前言

本文主要分析Yii2應用的啓動、運行的過程,主要包括如下三部分:入口腳本、啓動應用、運行應用。
在分析源碼的過程當中主要藉助了Xdebug工具。php


入口腳本

文件位置:web\index.phpweb

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

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

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

//加載應用配置
$config = require(__DIR__ . '/../config/web.php');

//生成應用並運行
(new yii\web\Application($config))->run();

分析: (new yii\web\Application($config))->run()數據庫

  1. 根據$config配置,生成應用實例(啓動應用:new yii\web\Application($config))
  2. 運行應用實例(運行應用:yii\web\Application::run())

啓動應用:new yii\web\Application($config)

分析:new yii\web\Application($config)
主要就是執行構造函數(代碼位置:vendor\yiisoft\yii2\base\Application.php)npm

public function __construct($config = [])
{
  //將Yii::$app指向當前的Application實例,所以後續就能夠經過Yii::$app來調用應用
  Yii::$app = $this;

  //調用yii\base\Module::setInstance($instance)
  //將Application實例自己記錄爲「已加載模塊」  
  //詳細參考本文後續「1-1分析」
  static::setInstance($this);

  //設置當前應用狀態
  $this->state = self::STATE_BEGIN;

  //進行一些預處理(根據$config配置應用)
  //詳細代碼位置:yii\base\Application::preInit(&$config)
  //主要根據config文件作了如下這些事情
  //1.判斷$config中是否有配置項‘id’(必須有,不然拋異常)
  //2.設置別名:@app,@vendor,@bower,@npm,@runtime
  //3.設置時間區域(timeZone)
  //4.自定義配置容器(Yii::$container)的屬性(由這裏咱們知道能夠自定義配置容器)
  //5.合併核心組件配置到自定義組件配置:數組$config['components']
  //(核心組件有哪些參考:yii\web\Application::coreComponents())
  //(注意:這個方法中$config使用了引用,因此合併$config['components']能夠改變$config原來的值)
  $this->preInit($config);

  //註冊ErrorHandler,這樣一旦拋出了異常或錯誤,就會由其負責處理
  //代碼位置:yii\base\Application::registerErrorHandler(&$config) 
  //詳細參考本文後續「1-2分析」
  $this->registerErrorHandler($config);

  //根據$config配置Application
  //而後執行yii\web\Application::init()(其實是執行yii\base\Application::init())
  //詳細參考本文後續「1-3分析」
  Component::__construct($config);
}

1-1分析:yii\base\Module::setInstance($instance)bootstrap

public static function setInstance($instance)
{
    if ($instance === null) {
        unset(Yii::$app->loadedModules[get_called_class()]);
    } else {
        //將Application實例自己記錄爲「已加載模塊」
        Yii::$app->loadedModules[get_class($instance)] = $instance;
    }
}

1-2分析:yii\base\Application::registerErrorHandler(&$config)segmentfault

//yii\web\Application的 $errorHandler 對應 yii\web\ErrorHandler(由yii\web\Application::coreComponents()得知)
//而yii\web\ErrorHandler繼承自yii\base\ErrorHandler
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);
        }
        //能夠看到就是根據$config['components']['errorHandler']來配置
        $this->set('errorHandler', $config['components']['errorHandler']);
        unset($config['components']['errorHandler']);
        //經過PHP函數註冊error handler(具體參考下面的yii\base\ErrorHandler::register())
        $this->getErrorHandler()->register();
    }
}



//默認config文件關於error handler的配置,這裏的配置會合併到Yii2自己對核心組件的定義中去(),核心組件的定義在yii\web\Application::coreComponents()
'components' => [
        'errorHandler' => [
            'errorAction' => 'site/error',
        ],
],



//yii\base\ErrorHandler::register()
public function register()
{   
    //該選項設置是否將錯誤信息做爲輸出的一部分顯示到屏幕,
    //或者對用戶隱藏而不顯示。
        ini_set('display_errors', false);
 
    //當拋出異常且沒有被catch的時候,由yii\base\ErrorHandler::handleException()處理
    set_exception_handler([$this, 'handleException']);
    if (defined('HHVM_VERSION')) {
        set_error_handler([$this, 'handleHhvmError']);
    } else {
        //當出現error的時候由yii\base\ErrorHandler::handleError()處理
        set_error_handler([$this, 'handleError']);
    }
    if ($this->memoryReserveSize > 0) {
        $this->_memoryReserve = str_repeat('x', $this->memoryReserveSize);
    }
    ////當出現fatal error的時候由yii\base\ErrorHandler::handleFatalError()處理
    register_shutdown_function([$this, 'handleFatalError']);
}

一個值得注意的地方:
在handleException()、handleError()、handleFatalError()中會直接或間接調用yii\web\ErrorHandle::renderException(),而在這個函數裏面,有如下這一行代碼
$result = Yii::$app->runAction($this->errorAction);
回顧上面config文件中對errorAction這個屬性的定義,咱們知道能夠經過自定義一個action用於在異常或錯誤時顯示自定義的出錯頁面
例如yii2中默認使用’site/error’這個action來處理

1-3分析:Component::__construct($config)
(代碼實際位置:vendor\yiisoft\yii2\base\Object.php)數組

public function __construct($config = [])
{
    if (!empty($config)) {
        //見下面的詳細分析
        Yii::configure($this, $config);
    }
    //跳轉到yii\base\Application::init(),見下面的分析
    $this->init();
}


//Yii::configure($object, $properties)分析
//實際上就是爲Application的屬性賦值
//注意:這裏會觸發一些魔術方法,間接調用了:
//yii\web\Application::setHomeUrl()
//yii\di\ServiceLocator::setComponents()(這個值得注意,詳細參考本文後續「1-3-1分析」)
//yii\base\Module::setModules()
public static function configure($object, $properties)
{
    foreach ($properties as $name => $value) {
        $object->$name = $value;
    }

    return $object;
}


yii\base\Application::init()分析
public function init()
{
    //設置當前應用狀態
    $this->state = self::STATE_INIT;
    //見下面的bootstrap()
    $this->bootstrap();
}

protected function bootstrap()
{
    //request是核心組件,這裏使用了依賴注入機制(ServiceLocator)來獲取request
    //注意:這裏是第一次調用request組件,會初始化該組件
    $request = $this->getRequest();
    //設置別名
    Yii::setAlias('@webroot', dirname($request->getScriptFile()));
    Yii::setAlias('@web', $request->getBaseUrl());
     
    //跳轉到yii\base\Application::bootstrap(),詳細參考本文後續「1-3-2分析」
    //在這裏主要是處理第三方擴展(extensions)和一些預啓動(bootstrap)
    parent::bootstrap();
}

1-3-1分析:yii\di\ServiceLocator::setComponents()瀏覽器

public function setComponents($components)
{
    foreach ($components as $id => $component) {
        //設置各個組件的定義,這樣後續就能經過依賴注入機制來獲取獲取組件了
        //應該還記得這裏的配置參數來源於config文件中的定義和yii\web\Application::coreComponents()中的定義
        $this->set($id, $component);
    }
}

1-3-2分析:yii\base\Application::bootstrap()yii2

protected function bootstrap()
{
    if ($this->extensions === null) {
        //@vendor/yiisoft/extensions.php是一些關於第三方擴展的配置,當用composer require安裝第三擴展的時候就會將新的擴展的相關信息記錄到該文件,這樣咱們就能夠在代碼中調用了
        $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);
            }
        }
        //進行一些必要的預啓動
        if (isset($extension['bootstrap'])) {
            $component = Yii::createObject($extension['bootstrap']);
            if ($component instanceof BootstrapInterface) {
                Yii::trace('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);
                $component->bootstrap($this);
            } else {
                Yii::trace('Bootstrap with ' . get_class($component), __METHOD__);
            }
        }
    }
    
    //預啓動,一般會包括‘log’組件,‘debug’模塊和‘gii’模塊(參考配置文件)
    foreach ($this->bootstrap as $class) {
        $component = null;
        if (is_string($class)) {
            if ($this->has($class)) {
                $component = $this->get($class);
            } elseif ($this->hasModule($class)) {
                $component = $this->getModule($class);
            } elseif (strpos($class, '\\') === false) {
                throw new InvalidConfigException("Unknown bootstrapping component ID: $class");
            }
        }
        if (!isset($component)) {
            $component = Yii::createObject($class);
        }

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

在完成了上面的流程以後,應用就算啓動成功了,能夠開始運行,處理請求了。cookie


運行應用:yii\web\Application::run()

分析:yii\base\Application::run()
在上面的構造函數執行完後,開始運行應用。即下面這行代碼的run()部分
(new yii\web\Application($config))->run();//(實際上執行的是yii\base\Application::run())

public function run()
{
    try {

        $this->state = self::STATE_BEFORE_REQUEST;
        //觸發事件’beforeRequest’,依次執行該事件的handler,
        $this->trigger(self::EVENT_BEFORE_REQUEST);

        $this->state = self::STATE_HANDLING_REQUEST;
        //處理請求,這裏的返回值是yii\web\Response實例
        //handleRequest(),詳細參考本文後續「2-1分析」
        $response = $this->handleRequest($this->getRequest());

        $this->state = self::STATE_AFTER_REQUEST;
        //觸發事件’afterRequest’,依次執行該事件的handler
        $this->trigger(self::EVENT_AFTER_REQUEST);

        $this->state = self::STATE_SENDING_RESPONSE;
        //發送響應,詳細參考本文後續「2-2分析」
        $response->send();

        $this->state = self::STATE_END;

        return $response->exitStatus;

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

    }
}

2-1分析: yii\web\Application::handleRequest()

public function handleRequest($request)
{
    if (empty($this->catchAll)) {
        try {
            //解析請求獲得路由和相應的參數,這裏會調用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();
            }
            //當解析請求出現異常時進行重定向
            return $this->getResponse()->redirect(Url::to($url, $e->scheme), $e->statusCode);
        }
    } else {
        //’catchAll’參數能夠在配置文件中自定義,可用於在項目須要臨時下線維護時給出一個統一的訪問路由
        $route = $this->catchAll[0];
        $params = $this->catchAll;
        unset($params[0]);
    }
    try {
        Yii::trace("Route requested: '$route'", __METHOD__);
        //記錄下當前請求的route
        $this->requestedRoute = $route;
        //執行路由相對應的action(yii\base\Module::runAction()詳細參考本文後續「2-1-1分析」)
        $result = $this->runAction($route, $params);
        if ($result instanceof Response) {
            return $result;
        } else {
            //若是action的返回結果不是Response的實例,則將結果封裝到Response實例的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);
    }
}

2-1-1分析: yii\base\Module::runAction()

public function runAction($route, $params = [])
{
    //根據路由建立Controller實例(詳細參考本文後續「2-1-1-1分析」)
    $parts = $this->createController($route);
    if (is_array($parts)) {
        /* @var $controller Controller */
        list($controller, $actionID) = $parts;
        $oldController = Yii::$app->controller;
        //設置當前的Controller實例
        Yii::$app->controller = $controller;
        //執行action(yii\base\Controller::runAction()詳細參考本文後續「2-1-1-2分析」)
        $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) . '".');
}

2-1-1-1分析: yii\base\Module::createController()

public function createController($route)
{
    //若是route爲空則設置route爲默認路由,這個能夠在配置文件中自定義
    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 = '';
    }

    //優先使用controllerMap,controllerMap能夠以下
    /*
     [
         'account' => 'app\controllers\UserController',
         'article' => [
             'class' => 'app\controllers\PostController',
             'pageTitle' => 'something new',
         ],
      ]
    */
    // module and controller map take precedence
    if (isset($this->controllerMap[$id])) {
        $controller = Yii::createObject($this->controllerMap[$id], [$id, $this]);
        return [$controller, $route];
    }
    
    //先判斷是否存在相應的模塊
    $module = $this->getModule($id);
    if ($module !== null) {
        //當存在模塊時,進行遞歸
        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 !== '') {
        //詳細見下面的代碼分析
        $controller = $this->createControllerByID($id . '/' . $route);
        $route = '';
    }
    //返回Controller實例和剩下的路由信息
    return $controller === null ? false : [$controller, $route];
}


    
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 (!preg_match('%^[a-z][a-z0-9\\-_]*$%', $className)) {
        return null;
    }
    if ($prefix !== '' && !preg_match('%^[a-z0-9_/]+$%i', $prefix)) {
        return null;
    }
    //進行一些轉換
    $className = str_replace(' ', '', ucwords(str_replace('-', ' ', $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實例
        $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;
}

2-1-1-2分析:yii\base\Controller::runAction()

public function runAction($id, $params = [])
{
    //建立action實例,詳細見下面的代碼
    $action = $this->createAction($id);
    if ($action === null) {
        throw new InvalidRouteException('Unable to resolve the request: ' . $this->getUniqueId() . '/' . $id);
    }

    Yii::trace('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當前所在的module,以及該module的全部祖先module(遞歸直至沒有祖先module)
    //而後從最初的祖先module開始,依次執行「模塊級」的beforeActio()
    //若是有beforeAction()沒有返回true, 那麼會中斷後續的執行
    // call beforeAction on modules
    foreach ($this->getModules() as $module) {
        if ($module->beforeAction($action)) {
            array_unshift($modules, $module);
        } else {
            $runAction = false;
            break;
        }
    }

    $result = null;

    //執行當前控制器的beforeAction,經過後再最終執行action
    //(若是前面「模塊級beforeAction」沒有所有返回true,則這裏不會執行)
    if ($runAction && $this->beforeAction($action)) {
        // run the action
        //(代碼位置:yii\base\InlineAction::runWithParams()詳細參考本文後續「2-1-1-2-1分析」和yii\base\Action::runWithParams()詳細參考本文後續「2-1-1-2-2分析」)
        $result = $action->runWithParams($params);


        //執行當前Controller的afterAction
        $result = $this->afterAction($action, $result);

        //從當前模塊開始,執行afterAction,直至全部祖先的afterAction
        // call afterAction on modules
        foreach ($modules as $module) {
            /* @var $module Module */
            $result = $module->afterAction($action, $result);
        }
    }

    if ($oldAction !== null) {
        $this->action = $oldAction;
    }

    //若是有beforeAction沒有經過,那麼會返回null
    return $result;
}


public function createAction($id)
{
    //默認action
    if ($id === '') {
        $id = $this->defaultAction;
    }

    //獨立action(Standalone Actions )
    $actionMap = $this->actions();
    if (isset($actionMap[$id])) {
        //返回一個action實例,一般是yii\base\Action的子類
        return Yii::createObject($actionMap[$id], [$id, $this]);
    } elseif (preg_match('/^[a-z0-9\\-_]+$/', $id) && strpos($id, '--') === false && trim($id, '-') === $id) {
        $methodName = 'action' . str_replace(' ', '', ucwords(implode(' ', explode('-', $id))));
        if (method_exists($this, $methodName)) {
            $method = new \ReflectionMethod($this, $methodName);
            if ($method->isPublic() && $method->getName() === $methodName) {
                //InlineAction封裝了將要執行的action的相關信息,該類繼承自yii\base\Action
                return new InlineAction($id, $this, $methodName);
            }
        }
    }

    return null;
}

2-1-1-2-1分析: yii\base\InlineAction::runWithParams()

public function runWithParams($params)
{
    //yii\web\Controller::bindActionParams()詳細參考本文後續「2-1-1-2-1-1分析」
    $args = $this->controller->bindActionParams($this, $params);
    Yii::trace('Running action: ' . get_class($this->controller) . '::' . $this->actionMethod . '()', __METHOD__);
    if (Yii::$app->requestedParams === null) {
        Yii::$app->requestedParams = $args;
    }
    
    //真正地調用開發者寫的Action代碼
    return call_user_func_array([$this->controller, $this->actionMethod], $args);
}

2-1-1-2-2分析:yii\base\Action::runWithParams()

public function runWithParams($params)
{
    if (!method_exists($this, 'run')) {
        throw new InvalidConfigException(get_class($this) . ' must define a "run()" method.');
    }
    //yii\web\Controller::bindActionParams()詳細參考本文後續「2-1-1-2-1-1分析」
    $args = $this->controller->bindActionParams($this, $params);
    Yii::trace('Running action: ' . get_class($this) . '::run()', __METHOD__);
    if (Yii::$app->requestedParams === null) {
        Yii::$app->requestedParams = $args;
    }
    if ($this->beforeRun()) {
        //執行獨立Action的run方法
        $result = call_user_func_array([$this, 'run'], $args);
        $this->afterRun();

        return $result;
    } else {
        return null;
    }
}

2-1-1-2-1-1分析:yii\web\Controller::bindActionParams()

public function bindActionParams($action, $params)
{
    if ($action instanceof InlineAction) {
        //若是是InlineAction則對Controller中相應的action方法進行反射
        $method = new \ReflectionMethod($this, $action->actionMethod);
    } else {
        //若是是獨立action則對該Action類的run方法進行反射
        $method = new \ReflectionMethod($action, 'run');
    }

    $args = [];
    $missing = [];
    $actionParams = [];
    //經過php提供的反射機制綁定Action的參數,同時還會判斷url中的參數是否知足要求
    foreach ($method->getParameters() as $param) {
        $name = $param->getName();
        if (array_key_exists($name, $params)) {
            if ($param->isArray()) {
                $args[] = $actionParams[$name] = (array) $params[$name];
            } elseif (!is_array($params[$name])) {
                $args[] = $actionParams[$name] = $params[$name];
            } else {
                throw new BadRequestHttpException(Yii::t('yii', 'Invalid data received for parameter "{param}".', [
                    'param' => $name,
                ]));
            }
            unset($params[$name]);
        } elseif ($param->isDefaultValueAvailable()) {
            $args[] = $actionParams[$name] = $param->getDefaultValue();
        } else {
            $missing[] = $name;
        }
    }

    if (!empty($missing)) {
        throw new BadRequestHttpException(Yii::t('yii', 'Missing required parameters: {params}', [
            'params' => implode(', ', $missing),
        ]));
    }

    $this->actionParams = $actionParams;

    return $args;
}

2-2分析: yii\web\Response::send()

public function send()
{
    if ($this->isSent) {
        return;
    }
    $this->trigger(self::EVENT_BEFORE_SEND);
    //預處理,詳見下面的代碼
    $this->prepare();
    $this->trigger(self::EVENT_AFTER_PREPARE);
    //發送http響應的頭部,詳見下面的代碼
    $this->sendHeaders();
    //發送http響應的主體,詳見下面的代碼
    $this->sendContent();
    $this->trigger(self::EVENT_AFTER_SEND);
    $this->isSent = true;
}


protected function prepare()
{
    if ($this->stream !== null) {
        return;
    }
    
    //使用formatter對相應進行處理,這個能夠在配置文件中自定義
    if (isset($this->formatters[$this->format])) {
        $formatter = $this->formatters[$this->format];
        if (!is_object($formatter)) {
            $this->formatters[$this->format] = $formatter = Yii::createObject($formatter);
        }
        if ($formatter instanceof ResponseFormatterInterface) {
            $formatter->format($this);
        } else {
            throw new InvalidConfigException("The '{$this->format}' response formatter is invalid. It must implement the ResponseFormatterInterface.");
        }
    } elseif ($this->format === self::FORMAT_RAW) {
        if ($this->data !== null) {
            $this->content = $this->data;
        }
    } else {
        throw new InvalidConfigException("Unsupported response format: {$this->format}");
    }
    
 
    //確保響應的content爲string
    if (is_array($this->content)) {
        throw new InvalidParamException('Response content must not be an array.');
    } elseif (is_object($this->content)) {
        if (method_exists($this->content, '__toString')) {
            $this->content = $this->content->__toString();
        } else {
            throw new InvalidParamException('Response content must be a string or an object implementing __toString().');
        }
    }
}


protected function sendHeaders()
{
    //判斷是否已經把頭部發送出去了
    if (headers_sent()) {
        return;
    }
    if ($this->_headers) {
        $headers = $this->getHeaders();
        //設置併發送http響應頭
        foreach ($headers as $name => $values) {
            $name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $name)));
            // set replace for first occurrence of header but false afterwards to allow multiple
            $replace = true;
            foreach ($values as $value) {
                //主要就是調用了PHP的header()函數
                header("$name: $value", $replace);
                $replace = false;
            }
        }
    }
    $statusCode = $this->getStatusCode();
    header("HTTP/{$this->version} {$statusCode} {$this->statusText}");
    //主要就是調用了PHP的setcookie()函數
    $this->sendCookies();
}


protected function sendContent()
{
    if ($this->stream === null) {
        //直接echo輸出內容
        echo $this->content;

        return;
    }

    set_time_limit(0); // Reset time limit for big files
    $chunkSize = 8 * 1024 * 1024; // 8MB per chunk

    if (is_array($this->stream)) {
        list ($handle, $begin, $end) = $this->stream;
        fseek($handle, $begin);
        while (!feof($handle) && ($pos = ftell($handle)) <= $end) {
            if ($pos + $chunkSize > $end) {
                $chunkSize = $end - $pos + 1;
            }
            echo fread($handle, $chunkSize);
            flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
        }
        fclose($handle);
    } else {
        while (!feof($this->stream)) {
            echo fread($this->stream, $chunkSize);
            flush();
        }
        fclose($this->stream);
    }
}

請求流程

聲明:如下內容轉載自‘http://www.yiichina.com/doc/g...

Yii2請求流程
1.用戶向入口腳本 web/index.php 發起請求。

2.入口腳本加載應用配置並建立一個應用實例去處理請求。

3.應用經過請求組件解析請求的路由。

4.應用建立一個控制器實例去處理請求。

5.控制器建立一個操做實例並針對操做執行過濾器。

6.若是任何一個過濾器返回失敗,則操做退出。

7.若是全部過濾器都經過,操做將被執行。

8.操做會加載一個數據模型,或許是來自數據庫。

9.操做會渲染一個視圖,把數據模型提供給它。

10.渲染結果返回給響應組件。

11.響應組件發送渲染結果給用戶瀏覽器。


應用主體生命週期

聲明:如下內容轉載自"http://www.yiichina.com/doc/guide/2.0/structure-applications"
在深刻研究了源碼以後,在此總結一下。當運行入口腳本處理請求時, 應用主體會經歷如下生命週期:

應用主體生命週期

  1. 入口腳本加載應用主體配置數組。
  2. 入口腳本建立一個應用主體實例:

    a.調用 preInit() 配置幾個高級別應用主體屬性, 好比 yii\\base\\Application::basePath。
      b.註冊 yii\\base\\Application::errorHandler 錯誤處理方法.
      c.配置應用主體屬性.
      d.調用 init() 初始化,該函數會調用 bootstrap() 運行引導啓動組件.
  3. 入口腳本調用 yii\base\Application::run() 運行應用主體:

    a.觸發 EVENT_BEFORE_REQUEST 事件。
       b.處理請求:解析請求 路由 和相關參數; 建立路由指定的模塊、控制器和動做對應的類,並運行動做。
       c.觸發 EVENT_AFTER_REQUEST 事件。
       d.發送響應到終端用戶.
  4. 入口腳本接收應用主體傳來的退出狀態並完成請求的處理。
相關文章
相關標籤/搜索