Yii2入口文件流程分析

    2018年12月05日 doc php 字数:6345

1 单一入口 index.php

文件名:basic/web/index.php

<?php

// 测试环境使用,部署到生产环境时注释掉以下两行
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');

// 引用自动加载
require __DIR__ . '/../vendor/autoload.php';
// 引用yii.php
require __DIR__ . '/../vendor/yiisoft/yii2/Yii.php';

// 获取web配置文件
$config = require __DIR__ . '/../config/web.php';

// 启动
(new yii\web\Application($config))->run();

2 Yii.php

文件路径:basic/vendor/yiisoft/yii2/Yii.php

<?php
require __DIR__ . '/BaseYii.php';
// yii是一个辅助类,提供常见的框架功能
// 通过补充yii内容,可以定制自己的框架功能
class Yii extends \yii\BaseYii
{
}
/* 
 * Yii_autoload执行过程
 * 1、先查看类是否在 Yii::$classMap 中存在
 * 2、存在直接调用getAlias生成类文件物理地址
 * 3、不存在将命名空间转换为实际路径调用
*/
spl_autoload_register(['Yii', 'autoload'], true, true);
// 核心类的类名和物理文件地址映射的hash数组
Yii::$classMap = require __DIR__ . '/classes.php';
/**
 * 依赖注入(Dependency Injection,DI)容器
 * 依赖注入容器知道怎样初始化并配置对象及其依赖的所有对象
 * 在Yii中使用DI解耦,常见注入方式:构造函数注入、属性注入
 * yii\di\Container继承了 
 * yii\base\Component继承了
 * yii\base\BaseObject
 * BaseObject实现了Configurable
 * Configurable是支持由可配置的类实现的接口
 * DI容器只支持 yii\base\Object 类
 * 如果你的类想放在DI容器里,那么必须继承自 yii\base\Object类
 */
Yii::$container = new yii\di\Container();

3 web/Application.php继承base/Application.php 执行构造方法

    public function __construct($config = [])
    {
        Yii::$app = $this;
		  // application对象放到注册树中
        static::setInstance($this);
        // 初始化执行状态
        $this->state = self::STATE_BEGIN;
        // 验证并初始化config配置
        $this->preInit($config);
        // 将errorHandler组件注册为PHP错误处理程序 
        $this->registerErrorHandler($config);
        // 调用Component
		  // 调用BaseObject
        // BaseObject下的__construct方法执行init方法
        // 该方法被下层类重写过,最终执行base\Application的init
        Component::__construct($config);
    }

4 base/Application.php中的init()方法

    public function init()
    {
        $this->state = self::STATE_INIT;
        $this->bootstrap();
    }

5 web/Application.php中的bootstrap()方法

    protected function bootstrap()
    {
        $request = $this->getRequest();
        // 定义别名
        Yii::setAlias('@webroot', dirname($request->getScriptFile()));
        Yii::setAlias('@web', $request->getBaseUrl());
        // 调用base/Application.php中的bootstrap()
        parent::bootstrap();
    }

6 base/Application.php中的bootstrap()方法

protected function bootstrap() {
        
        // 如果扩展文件为空直接加载扩展清单文件
        if ($this->extensions === null) {
            $file = Yii::getAlias('@vendor/yiisoft/extensions.php');
            $this->extensions = is_file($file) ? include $file : [];
        }
        // 在扩展返回的数组中,使用createObject实例化对象
        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::debug('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);
                    $component->bootstrap($this);
                } else {
                    Yii::debug('Bootstrap with ' . get_class($component), __METHOD__);
                }
            }
        }
		// 创建并运行各个组件
		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__);
            }
        }
    }

7 base/Application.php中的run()方法

public function run()
    {
        try {
            $this->state = self::STATE_BEFORE_REQUEST;
            // 将此事件通知给绑定到这个事件的观察者,绑定事件的方法
            $this->trigger(self::EVENT_BEFORE_REQUEST);
            // 开始处理请求参数
            $this->state = self::STATE_HANDLING_REQUEST;
            $response = $this->handleRequest($this->getRequest());

            $this->state = self::STATE_AFTER_REQUEST;
            $this->trigger(self::EVENT_AFTER_REQUEST);
            // 处理返回响应
            $this->state = self::STATE_SENDING_RESPONSE;
            $response->send();

            $this->state = self::STATE_END;

            return $response->exitStatus;
        } catch (ExitException $e) {
            $this->end($e->statusCode, isset($response) ? $response : null);
            return $e->statusCode;
        }
    }

8 yii\web\Application::handleRequest()方法

public function handleRequest($request)
    {
        if (empty($this->catchAll)) {
            try {
                // resolve 方法调用 urlManager 对 url 进行解析
                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::debug("Route requested: '$route'", __METHOD__);
            $this->requestedRoute = $route;
            // 根据route访问对应的 module/controller/action
            // 入参类似 : yii\base\Module::runAction('country/index', ['r' => 'country/index'])
            $result = $this->runAction($route, $params);
            if ($result instanceof Response) {
                return $result;
            }

            $response = $this->getResponse();
            if ($result !== null) {
                $response->data = $result;
            }

            return $response;
        } catch (InvalidRouteException $e) {
            throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'), $e->getCode(), $e);
 	}
    }

9 yii\base\Module::runAction()方法

public function runAction($route, $params = []){
			
        // 重点关注创建controller方法
        // 如果route为空,则使用defaultRouter属性
        // router不为空,查看controllerMap是否命中
        // 未命中则调用yii/base/Module::getModule查看
        // 查看route中是否有module存在
        // 存在则直接调用yii/base/Module::createController
        // 否则yii/base/Module::createControllerByID
        $parts = $this->createController($route);
        if (is_array($parts)) {
            /* @var $controller Controller */
            list($controller, $actionID) = $parts;
            $oldController = Yii::$app->controller;
            Yii::$app->controller = $controller;
            // 调用形式为
            // yii\base\Controller::runAction('index', ['r' => 'country/index'])
            $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) . '".');
    }

10 yii\base\Controller::runAction() 方法

public function runAction($id, $params = [])
    {
        // 重点看createAction
        // 如果id为空,则访问默认action
        // id不为空,看Controller::actions 方法中是否有配置
        // 如果有,直接使用配置中的class
        // 利用反射(ReflectionMethod)查看调用方法是否存在
        // 如果是,返回 yii\base\InlineAction 的实例 
        $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;

        // 调用所有加载module中的beforeAction
        foreach ($this->getModules() as $module) {
            if ($module->beforeAction($action)) {
                array_unshift($modules, $module);
            } else {
                $runAction = false;
                break;
            }
        }

        $result = null;
		  if ($runAction && $this->beforeAction($action)) {
            // 运行action
            $result = $action->runWithParams($params);

            $result = $this->afterAction($action, $result);

            // 调用所有module的afterAction方法
            foreach ($modules as $module) {
                /* @var $module Module */
                $result = $module->afterAction($action, $result);
            }
        }

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

        return $result;
    }

11 yii2/base/InlineAction下runWithParams()

public function runWithParams($params) {
        $args = $this->controller->bindActionParams($this, $params);
        Yii::debug('Running action: ' . get_class($this->controller) . '::' . $this->actionMethod . '()', __METHOD__);
        if (Yii::$app->requestedParams === null) {
            Yii::$app->requestedParams = $args;
        }
        // 回调形式类似如下call_user_func_array([app\controllers\CountryController, 'actionIndex'], [])
        return call_user_func_array([$this->controller, $this->actionMethod], $args);
    }

12 进入到对应的业务controller中处理