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

前言

本文主要分析Yii2应用的启动、运行的过程,主要包括以下三部分:入口脚本、启动应用、运行应用。
在分析源码的过程中主要借助了Xdebug工具。


入口脚本

文件位置:web\index.php

//定义全局变量
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)

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)

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)

//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()

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__);
        }
    }
}

在完成了上面的流程之后,应用就算启动成功了,可以开始运行,处理请求了。


运行应用: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. 入口脚本接收应用主体传来的退出状态并完成请求的处理。
阅读 4.4k

推荐阅读
Darkgel
用户专栏

Darkgel的专栏,记录程序人生。

15 人关注
12 篇文章
专栏主页