Yii2 运行原理分析(源码)

1、入口文件index.php
2、composer的自动加载
index.php 

require __DIR__ . '/../vendor/autoload.php';
3、yii框架文件的自动加载
yii.php

require __DIR__ . '/../vendor/yiisoft/yii2/Yii.php';
4、yii核心类映射,该映射关系是由build命令自动生成的,不需要个开发者管
yii.php

Yii::$classMap = require __DIR__ . '/classes.php';
5、加载配置文件 config/web.php
index.php

$config = require __DIR__ . '/../config/web.php';
6、传递配置文件获取应用实例,并运行应用实例
index.php

(new yii\web\Application($config))->run();
7、可以看到,实例化应用时,将自定义配置文件传了过去,这一步进行了一些校验和设置,以及将核心组件和自定义组件合并
比如关于配置ID、bashpath的校验

比如对timezone、errohandle的设置
vendor/yiisoft/yii2/base/Application.php(Application的父类)

public function __construct($config = [])
 {
        // 将应用实例自身赋值给Yii::$app
        Yii::$app = $this;

        // 设置此模块类的当前请求实例
        static::setInstance($this);

        // 标识请求处理生命周期中的当前应用程序状态,由程序管理
        $this->state = self::STATE_BEGIN;

        // 预初始化应用程序
        $this->preInit($config);

        // 注册错误处理器
        $this->registerErrorHandler($config);

        Component::__construct($config);
 }
/**
     * 预初始化应用程序(由构造函数调用该方法)
     * @param array $config the application configuration
     * @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing.
     */
    public function preInit(&$config)
    {
        // 校验配置文件中是否设置配置ID,如果没有设置,则抛出异常
        if (!isset($config['id'])) {
            throw new InvalidConfigException('The "id" configuration for the Application is required.');
        }

        // 校验配置文件中是否设置程序的基本路径。如果有设置,则将基本路径设置到Module类(Application类的父类)属性中,否则抛出异常
        if (isset($config['basePath'])) {
            $this->setBasePath($config['basePath']);
            unset($config['basePath']);
        } else {
            throw new InvalidConfigException('The "basePath" configuration for the Application is required.');
        }

        // 校验是否在配置文件中设置,并进行设置
        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();
        }

        if (isset($config['timeZone'])) {
            $this->setTimeZone($config['timeZone']);
            unset($config['timeZone']);
        } elseif (!ini_get('date.timezone')) {
            $this->setTimeZone('UTC');
        }

        if (isset($config['container'])) {
            $this->setContainer($config['container']);
            unset($config['container']);
        }

        // 将核心组件与自定义组件合并
        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'];
            }
        }
    }
8、运行实例
vendor/yiisoft/yii2/base/Application.php

/**
     * 运行应用程序
     * 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;
            $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.1 可以看到,首先执行了请求前的事件触发

$this->state = self::STATE_BEFORE_REQUEST;

$this->trigger(self::EVENT_BEFORE_REQUEST);

8.2 然后,处理请求

$this->state = self::STATE_HANDLING_REQUEST;

$response = $this->handleRequest($this->getRequest());

8.3 之后,再执行请求后的时间触发

$this->state = self::STATE_AFTER_REQUEST;

$this->trigger(self::EVENT_AFTER_REQUEST);

8.4 发送响应给客户端

$this->state = self::STATE_SENDING_RESPONSE;

$response->send();

8.5 将应用状态标识为应用程序已结束

$this->state = self::STATE_END;
9、分析处理请求的过程
/**
 * 处理请求
 * @param Request $request
 * @return Response the resulting response
 * @throws NotFoundHttpException if the requested route is invalid
 */
public function handleRequest($request)
{
    // 解析请求为路由和参数(如果设置了catchAll,则将$route和$params重置)
    if (empty($this->catchAll)) {
        try {
            // resolve方法的作用是将当前请求解析为路由和关联的参数
            list($route, $params) = $request->resolve();
        } catch (UrlNormalizerRedirectException $e) {
            $url = $e->url;
            if (is_array($url)) {
                if (isset($url[0])) {
                    $url[0] = '/' . ltrim($url[0], '/');
                }
                $url += $request->getQueryParams();
            }
            return $this->getResponse()->redirect(Url::to($url, $e->scheme), $e->statusCode);
        }
    } else {
        // 如果设置了维护(catchAll),则重置$route和$params为维护的路由和参数
        $route = $this->catchAll[0];
        $params = $this->catchAll;
        unset($params[0]);
    }

    try {
        // 记录调试消息
        Yii::debug("Route requested: '$route'", __METHOD__);
        // 设置路由
        $this->requestedRoute = $route;
        // 运行由路由指定的控制器操作
        $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);
    }
}
10、可以看到,实际处理请求的是runAction方法
public function runAction($route, $params = [])
{
    $parts = $this->createController($route);
    if (is_array($parts)) {
        /* @var $controller Controller */
        list($controller, $actionID) = $parts;
        $oldController = Yii::$app->controller;
        Yii::$app->controller = $controller;
        $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) . '".');
}
11、而其中最重要的就是createController方法
public function createController($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 = '';
    }

    // 模块和控制器映射优先
    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 = $this->createControllerByID($id);
    if ($controller === null && $route !== '') {
        $controller = $this->createControllerByID($id . '/' . $route);
        $route = '';
    }

    return $controller === null ? false : [$controller, $route];
}

可以很容易的看到,这个方法表达的大致意思就是

如果路由是空,比如访问 www.haveyb.com/ ,则跳转到默认路由

否则继续判断是否有设置模块和控制器映射,如果有,则直接返回

否则就正常处理请求
12、返回响应