资讯专栏INFORMATION COLUMN

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

ghnor / 1316人阅读

摘要:在分析源码的过程中主要借助了工具。运行应用分析在上面的构造函数执行完后,开始运行应用。发送响应到终端用户入口脚本接收应用主体传来的退出状态并完成请求的处理。

前言

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


入口脚本

文件位置:webindex.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 yiiwebApplication($config))->run();

分析: (new yiiwebApplication($config))->run()

根据$config配置,生成应用实例(启动应用:new yiiwebApplication($config))

运行应用实例(运行应用:yiiwebApplication::run())


启动应用:new yiiwebApplication($config)

分析:new yiiwebApplication($config)
主要就是执行构造函数(代码位置:vendoryiisoftyii2aseApplication.php)

public function __construct($config = [])
{
  //将Yii::$app指向当前的Application实例,因此后续就可以通过Yii::$app来调用应用
  Yii::$app = $this;

  //调用yiiaseModule::setInstance($instance)
  //将Application实例本身记录为“已加载模块”  
  //详细参考本文后续“1-1分析”
  static::setInstance($this);

  //设置当前应用状态
  $this->state = self::STATE_BEGIN;

  //进行一些预处理(根据$config配置应用)
  //详细代码位置:yiiaseApplication::preInit(&$config)
  //主要根据config文件做了以下这些事情
  //1.判断$config中是否有配置项‘id’(必须有,否则抛异常)
  //2.设置别名:@app,@vendor,@bower,@npm,@runtime
  //3.设置时间区域(timeZone)
  //4.自定义配置容器(Yii::$container)的属性(由这里我们知道可以自定义配置容器)
  //5.合并核心组件配置到自定义组件配置:数组$config["components"]
  //(核心组件有哪些参考:yiiwebApplication::coreComponents())
  //(注意:这个方法中$config使用了引用,所以合并$config["components"]可以改变$config原来的值)
  $this->preInit($config);

  //注册ErrorHandler,这样一旦抛出了异常或错误,就会由其负责处理
  //代码位置:yiiaseApplication::registerErrorHandler(&$config) 
  //详细参考本文后续“1-2分析”
  $this->registerErrorHandler($config);

  //根据$config配置Application
  //然后执行yiiwebApplication::init()(实际上是执行yiiaseApplication::init())
  //详细参考本文后续“1-3分析”
  Component::__construct($config);
}

1-1分析:yiiaseModule::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分析:yiiaseApplication::registerErrorHandler(&$config)

//yiiwebApplication的 $errorHandler 对应 yiiwebErrorHandler(由yiiwebApplication::coreComponents()得知)
//而yiiwebErrorHandler继承自yiiaseErrorHandler
protected function registerErrorHandler(&$config)
{
    if (YII_ENABLE_ERROR_HANDLER) {
        if (!isset($config["components"]["errorHandler"]["class"])) {
            echo "Error: no errorHandler component is configured.
";
            exit(1);
        }
        //可以看到就是根据$config["components"]["errorHandler"]来配置
        $this->set("errorHandler", $config["components"]["errorHandler"]);
        unset($config["components"]["errorHandler"]);
        //通过PHP函数注册error handler(具体参考下面的yiiaseErrorHandler::register())
        $this->getErrorHandler()->register();
    }
}



//默认config文件关于error handler的配置,这里的配置会合并到Yii2本身对核心组件的定义中去(),核心组件的定义在yiiwebApplication::coreComponents()
"components" => [
        "errorHandler" => [
            "errorAction" => "site/error",
        ],
],



//yiiaseErrorHandler::register()
public function register()
{   
    //该选项设置是否将错误信息作为输出的一部分显示到屏幕,
    //或者对用户隐藏而不显示。
        ini_set("display_errors", false);
 
    //当抛出异常且没有被catch的时候,由yiiaseErrorHandler::handleException()处理
    set_exception_handler([$this, "handleException"]);
    if (defined("HHVM_VERSION")) {
        set_error_handler([$this, "handleHhvmError"]);
    } else {
        //当出现error的时候由yiiaseErrorHandler::handleError()处理
        set_error_handler([$this, "handleError"]);
    }
    if ($this->memoryReserveSize > 0) {
        $this->_memoryReserve = str_repeat("x", $this->memoryReserveSize);
    }
    ////当出现fatal error的时候由yiiaseErrorHandler::handleFatalError()处理
    register_shutdown_function([$this, "handleFatalError"]);
}

一个值得注意的地方:
在handleException()、handleError()、handleFatalError()中会直接或间接调用yiiwebErrorHandle::renderException(),而在这个函数里面,有以下这一行代码
$result = Yii::$app->runAction($this->errorAction);
回顾上面config文件中对errorAction这个属性的定义,我们知道可以通过自定义一个action用于在异常或错误时显示自定义的出错页面
例如yii2中默认使用’site/error’这个action来处理

1-3分析:Component::__construct($config)
(代码实际位置:vendoryiisoftyii2aseObject.php)

public function __construct($config = [])
{
    if (!empty($config)) {
        //见下面的详细分析
        Yii::configure($this, $config);
    }
    //跳转到yiiaseApplication::init(),见下面的分析
    $this->init();
}


//Yii::configure($object, $properties)分析
//实际上就是为Application的属性赋值
//注意:这里会触发一些魔术方法,间接调用了:
//yiiwebApplication::setHomeUrl()
//yiidiServiceLocator::setComponents()(这个值得注意,详细参考本文后续“1-3-1分析”)
//yiiaseModule::setModules()
public static function configure($object, $properties)
{
    foreach ($properties as $name => $value) {
        $object->$name = $value;
    }

    return $object;
}


yiiaseApplication::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());
     
    //跳转到yiiaseApplication::bootstrap(),详细参考本文后续“1-3-2分析”
    //在这里主要是处理第三方扩展(extensions)和一些预启动(bootstrap)
    parent::bootstrap();
}

1-3-1分析:yiidiServiceLocator::setComponents()

public function setComponents($components)
{
    foreach ($components as $id => $component) {
        //设置各个组件的定义,这样后续就能通过依赖注入机制来获取获取组件了
        //应该还记得这里的配置参数来源于config文件中的定义和yiiwebApplication::coreComponents()中的定义
        $this->set($id, $component);
    }
}

1-3-2分析:yiiaseApplication::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__);
        }
    }
}

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


运行应用:yiiwebApplication::run()

分析:yiiaseApplication::run()
在上面的构造函数执行完后,开始运行应用。即下面这行代码的run()部分
(new yiiwebApplication($config))->run();//(实际上执行的是yiiaseApplication::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;
        //处理请求,这里的返回值是yiiwebResponse实例
        //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分析: yiiwebApplication::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(yiiaseModule::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分析: yiiaseModule::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(yiiaseController::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分析: yiiaseModule::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" => "appcontrollersUserController",
         "article" => [
             "class" => "appcontrollersPostController",
             "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, "yiiaseController")) {
        //通过依赖注入容器获得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 yiiaseController.");
    }
    return null;
}

2-1-1-2分析:yiiaseController::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
        //(代码位置:yiiaseInlineAction::runWithParams()详细参考本文后续“2-1-1-2-1分析”和yiiaseAction::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实例,通常是yiiaseAction的子类
        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的相关信息,该类继承自yiiaseAction
                return new InlineAction($id, $this, $methodName);
            }
        }
    }

    return null;
}

2-1-1-2-1分析: yiiaseInlineAction::runWithParams()

public function runWithParams($params)
{
    //yiiwebController::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分析:yiiaseAction::runWithParams()

public function runWithParams($params)
{
    if (!method_exists($this, "run")) {
        throw new InvalidConfigException(get_class($this) . " must define a "run()" method.");
    }
    //yiiwebController::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分析:yiiwebController::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分析: yiiwebResponse::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...’


1.用户向入口脚本 web/index.php 发起请求。

2.入口脚本加载应用配置并创建一个应用实例去处理请求。

3.应用通过请求组件解析请求的路由。

4.应用创建一个控制器实例去处理请求。

5.控制器创建一个操作实例并针对操作执行过滤器。

6.如果任何一个过滤器返回失败,则操作退出。

7.如果所有过滤器都通过,操作将被执行。

8.操作会加载一个数据模型,或许是来自数据库。

9.操作会渲染一个视图,把数据模型提供给它。

10.渲染结果返回给响应组件。

11.响应组件发送渲染结果给用户浏览器。


应用主体生命周期

声明:以下内容转载自"http://www.yiichina.com/doc/guide/2.0/structure-applications"
在深入研究了源码之后,在此总结一下。当运行入口脚本处理请求时, 应用主体会经历以下生命周期:

入口脚本加载应用主体配置数组。

入口脚本创建一个应用主体实例:

  a.调用 preInit() 配置几个高级别应用主体属性, 比如 yiiaseApplication::basePath。
  b.注册 yiiaseApplication::errorHandler 错误处理方法.
  c.配置应用主体属性.
  d.调用 init() 初始化,该函数会调用 bootstrap() 运行引导启动组件.

入口脚本调用 yiiaseApplication::run() 运行应用主体:

   a.触发 EVENT_BEFORE_REQUEST 事件。
   b.处理请求:解析请求 路由 和相关参数; 创建路由指定的模块、控制器和动作对应的类,并运行动作。
   c.触发 EVENT_AFTER_REQUEST 事件。
   d.发送响应到终端用户.

入口脚本接收应用主体传来的退出状态并完成请求的处理。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/26026.html

相关文章

  • Yii2框架源码分析之如何实现注册和登录

    摘要:在用户注册的时候是为空的,当用户忘记密码在登录页面点击后生成的,用来给用法发送邮件后重置密码时进行验证。如有错误,不吝赐教。 注册 在advanced模板中,进入frontend/index.php?r=site%2Fsignup页面,可以看到框架的注册页面showImg(https://segmentfault.com/img/bVDEaZ?w=300&h=235); 填写完User...

    chemzqm 评论0 收藏0
  • Yii2源码分析辅助记录

    get_called_class get_class call_user_func strncmp method_exists property_exists array_unshift get_class

    马龙驹 评论0 收藏0
  • yii2框架中的di容器源码中了解反射的作用

    摘要:反射简介参考官方简介的话,具有完整的反射,添加了对类接口函数方法和扩展进行反向工程的能力。此外,反射提供了方法来取出函数类和方法中的文档注释。 反射简介 参考官方简介的话,PHP 5 具有完整的反射 API,添加了对类、接口、函数、方法和扩展进行反向工程的能力。 此外,反射 API 提供了方法来取出函数、类和方法中的文档注释。 YII2框架中示例 对于yii2框架,应该都知道di容器,...

    dantezhao 评论0 收藏0
  • 列表——表头自定义显示字段

    摘要:今天我就来讲讲插件的使用,它是如何实现列表表头自定义显示字段的,我把我的经验分享出来,满足一下不懂英语的人,给你们搭个快车。需求分析实现列表表头自定义显示字段,自定义表头排序。 序言 Yii2框架的扩展性能真的很不错,很多效果都可以通过插件去实现,你想不到的老外都帮你想好了,于是,人群中就流传了这么一句话:效果不会写不要紧,会用插件也不错。GitHub是一个庞大而且开放的资源库,平时有...

    Yangyang 评论0 收藏0
  • Yii2系列教程六:集成编辑器

    摘要:而这些问题目前的最好解决方案就是集成一个编辑器,鉴于大家这里不是指程序员都是喜欢所见即所得,所以,这里我主要是演示怎么集成所见即所得的富文本编辑器。 原文来自: https://jellybool.com/post/programming-with-yii2-rich-text-input-with-redactor 首先,很惭愧的是,前几天都出去外面玩了,没有及时更新教程,...

    xiaochao 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<