@原文urlhttp://www.yiichina.com/code/893

http://www.yiichina.com/tutorial/42

yii2系列视频教程 http://i.youku.com/i/UMzY4MTgyNDI3Ng==/videos?spm=a2hzp.8244740.0.0.NT7LpF

  要想了解框架的基本原理,最基本的一个概念必须要弄懂,否则就会不知道文件怎么加载进来的,命名空间怎么找到相应的类的,这个概念就是依赖注入和容器,这里有几篇文章写的相当不错,不了解的人可以看下这两篇文章http://www.yiichina.com/tutorial/112http://www.yiichina.com/topic/6445我们先把整个请求流程走下来,之后我们在对各个环节的细节方面进行详细的源码分析

  

  按照这个流程,咱们现在开始。

  一,入口脚本,类的自动加载autoload(代码不懂得地方,可以用断点进行调试)

  因为我用的advanced版本,第一步就是找到入口脚本@frontend/web/index.php

<?php
//定义环境,测试环境还是生产环境
defined('YII_DEBUG') or define('YII_DEBUG', true); defined('YII_ENV') or define('YII_ENV', 'dev');
//注册yii的自动加载函数 //
require(__DIR__ . '/../../vendor/autoload.php'); //因为对于yii来说,Composer的自动加载是不需要的,可以注释掉。因为yii用的是自己定义自动加载函数,即Yii.php require(__DIR__ . '/../../vendor/yiisoft/yii2/Yii.php'); require(__DIR__ . '/../../common/config/bootstrap.php'); require(__DIR__ . '/../config/bootstrap.php'); //引用配置文件 $config = yii\helpers\ArrayHelper::merge( require(__DIR__ . '/../../common/config/main.php'), require(__DIR__ . '/../../common/config/main-local.php'), require(__DIR__ . '/../config/main.php'), require(__DIR__ . '/../config/main-local.php') ); (new yii\web\Application($config))->run(); //这里实例化的是vendor\yiisoft\yii2\base\Application.php这个类,这里会自动
include(YII_PATH.’/base/Application.php’),将上边配置信息数组$config传递给Application创建出对象,并执行对象的run() 方法启动框架。
$config先被传递给Application的构造函数,构造函数中Yii::$app = $this;将自身的实例对象赋给Yii的静态成员$app,以后可以通过 Yii::app() 来取得。

  第一行中,debug是否开启。

  第二行中,可以根据自己的环境,配置为'dev','prod','test'模式(开发环境,生产环境,测试环境)
  第六行,类加载(类的自动加载autoload

  接下来进行index.php中的require(DIR . '/../vendor/yiisoft/yii2/Yii.php');代码分析。

  @Yii.php文件目录:\yii根目录\vendor\yiisoft\yii2\Yii.php

  Yii类是YiiBase类的完全继承:

  class Yii extends BaseYii { }

  系统的全局访问都是通过Yii类(即BaseYii类)来实现的,Yii类的成员和方法都是static类型。

  Yii.php代码如下:

  @BaseYii.php文件目录:\yii根目录\vendor\yiisoft\yii2\BaseYii.php

<?php

require(__DIR__ . '/BaseYii.php');

class Yii extends \yii\BaseYii
{
}

spl_autoload_register(['Yii', 'autoload'], true, true);
Yii::$classMap = require(__DIR__ . '/classes.php');  //位于vendor\yiisoft\yii2\classes.php,自动加载函数使用,classes.php中主要是
yii需要用的一些组件,例如UrlManager组件负责处理网页请求路由到对应的控制器,UploadedFile组件用于文件上传。 Yii::$container = new yii\di\Container();  //yii容器,一个很重要的知识点,di即依赖注入。

  ---上边代码解释:

  

  可以看到代码class Yii extends \yii\BaseYii是继承于BaseYii的。(注意,此时系统还不会自己按照命名空间类的规则去查找某个文件夹下的问下,接下来会分析)
之后spl_autoload_register(['Yii', 'autoload'], true, true);对Yii中的autoload函数进行注册,因为这个函数是注册,所以用到的时候才会触发这个函数。所以接下来往下分析,Yii::$classMap = require(DIR . '/classes.php');,会看到,这个文件里的数组很庞大,对,就是这样,就像前面介绍的vendor/autoload.php一样,为了类的查找时间,系统已经将常用到的类的路径预装进来。接下来是Yii::$container = new yii\di\Container();,就是这个容器解决了很多的问题,如系统不会重复加载一个类实例,会自动查找依赖关系等等。借用这个机制,就可以找到相应的类,关于依赖注入后续文章再进行详细说明。

  Yii利用PHP5提供的spl库来完成类的自动加载。例如:spl_autoload_register(array(‘BaseYii’,'autoload’));

  将BaseYii类(位于vendor\yiisoft\yii2\BaseYii.php)的静态方法autoload 注册为类加载器。 PHP autoload 的简单原理就是执行 new 创建对象或通过类名访问静态成员时,系统将类名传递给被注册的类加载器函数,类加载器函数根据类名自行找到对应的类文件并include 。具体原理看下边代码:

  下面是BaseYii类的autoload方法(关键方法):

public static function autoload($className)
    {
        if (isset(static::$classMap[$className])) {
            $classFile = static::$classMap[$className];
            if ($classFile[0] === '@') {
                $classFile = static::getAlias($classFile);
            }
        } elseif (strpos($className, '\\') !== false) {
            $classFile = static::getAlias('@' . str_replace('\\', '/', $className) . '.php', false);
            if ($classFile === false || !is_file($classFile)) {
                return;
            }
        } else {
            return;
        }

        include($classFile);  // 传入类名直接include

        if (YII_DEBUG && !class_exists($className, false) && !interface_exists($className, false) && !trait_exists($className, false)) {
            throw new UnknownClassException("Unable to find '$className' in file: $classFile. Namespace missing?");
        }
    }

  关于static::这种延迟加载的问题,这里不做描述,读者可以自己去查找相关概念。
  代码中解释到如果在Yii::classMap中(也就是classes.php),如果找到该类,就直接inlude,如果没有的话,就去aliases数组中查找,如果还是查找不到的话,那容器就派上用场了。容器里也找不到,那就只能抛出错误了。
  回头重新看入口文件index.php,其实

require(__DIR__ . '/../vendor/autoload.php');
require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php');

$config = require(__DIR__ . '/../config/web.php');

  这几行没有涉及到教程里提到的加载应用配置,至此只是解决了类的自动加载问题。因为YII框架是个非常庞大的框架,里面牵扯太多的文件和路径,不先解决这个问题,只是简单的require就能把人累死。

  ***下边是Aplication类的的实例化以及执行过程***

  

  ---Yii\base\application中构造函数代码解释:

  @文件路径:\yii根目录\vendor\yiisoft\yii2\base\Application.php

  

  ---上边的id,timeZone设置,是在\yii根目录\vendor\yiisoft\yii2\base\Application.php中的preInit函数中设置的。

  ---下边是\yii根目录\vendor\yiisoft\yii2\base\Application.php类的构造函数

public function __construct($config = [])
    {
        Yii::$app = $this;
        static::setInstance($this);

        $this->state = self::STATE_BEGIN;   //设置执行状态

        $this->preInit($config);    //预初始化

        $this->registerErrorHandler($config);   //设置并注册异常错误处理函数

        Component::__construct($config);    //实现依赖注入并调用init函数,注意,这里Component是没有构造函数的,所以这里调用的是Component父类object的
构造函数
}

  ---Yii\base\application中init函数执行流程

  注意,自举函数执行的是web\application中的bootstrap函数

protected function bootstrap()
    {
        $request = $this->getRequest();  //实例化request对象
        Yii::setAlias('@webroot', dirname($request->getScriptFile()));  //设置别名
        Yii::setAlias('@web', $request->getBaseUrl());    //设置别名

        parent::bootstrap();  //执行父类中的bootstrap函数
    }

  

  ---run函数流程

  

   ---run函数代码解释:

public function run()
    {
        try {

            $this->state = self::STATE_BEFORE_REQUEST;  //设置执行状态
            $this->trigger(self::EVENT_BEFORE_REQUEST);     //调用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);  //调用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;

        }
    }

  ---上边run函数中handleRequest函数执行流程

  ---\yii根目录\vendor\yiisoft\yii2\base\Application.phpabstract public function handleRequest($request);是一个抽象函数。

  所以这里请求的是\yii根目录\vendor\yiisoft\yii2\web\Application.php下的handleRequest

  

  ---handleRequest代码如下:

public function handleRequest($request)
{
    if (empty($this->catchAll)) {   //公有属性,用于改变控制器,正产流程都是true,走下边代码
        list ($route, $params) = $request->resolve();   //获取路由和参数,路由用户获取控制器
    } else {
        $route = $this->catchAll[0];    //路由
        $params = $this->catchAll;  //路由参数
        unset($params[0]);
    }
    try {
        Yii::trace("Route requested: '$route'", __METHOD__);
        $this->requestedRoute = $route; //设置属性
        $result = $this->runAction($route, $params);    //调用控制器和方法,执行业务逻辑
        /**
         * 执行结果赋值给$response->data,并返回给response对象
         */
        if ($result instanceof Response) {
            return $result;
        } else {
            $response = $this->getResponse(); //如果上边判断不是一个response对象,这里获取一个
            if ($result !== null) {
                $response->data = $result;
            }

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

  

  ---resolve()函数代码如下:

public function resolve()
{
    $result = Yii::$app->getUrlManager()->parseRequest($this);  //返回路由信息
    /**
     * 组织路由信息
     */
    if ($result !== false) {
        list ($route, $params) = $result;
        if ($this->_queryParams === null) {
            $_GET = $params + $_GET; // preserve numeric keys 参数合并到get
        } else {
            $this->_queryParams = $params + $this->_queryParams;    //参数合并到GET$this->_queryParams;
        }
        return [$route, $this->getQueryParams()];   //返回route和(GET或this->getQueryParams()数组
    } else {
        throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'));  //$result为空,抛出异常
    }
}

  ---\yii根目录\vendor\yiisoft\yii2\web\Application.php中handleRequest函数中的runAction函数代码

  

---\yii根目录\vendor\yiisoft\yii2\web\Application.php中handleRequest函数中的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;    //做一下备份,备份到$oldController
        Yii::$app->controller = $controller;
        $result = $controller->runAction($actionID, $params); //调用这个控制器的执行方法id和参数
        Yii::$app->controller = $oldController;

        return $result; //返回执行结果
    } else {
        $id = $this->getUniqueId();
        throw new InvalidRouteException('Unable to resolve the request "' . ($id === '' ? $route : $id . '/' . $route) . '".');
    }
}
---createController函数代码如下:
public function createController($route)
{
    if ($route === '') {    //判断route是否为空
        $route = $this->defaultRoute;   //defaultRoute默认是site
    }

    //下边会进行多个判断(字符判断)
    // 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); //explode() 函数把字符串打散为数组。
    } else {
        $id = $route;
        $route = '';
    }

    // 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 = $this->createControllerByID($id);
    if ($controller === null && $route !== '') {
        $controller = $this->createControllerByID($id . '/' . $route);
        $route = '';
    }

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

  ---控制器runAction函数流程
  ---\yii根目录\vendor\yiisoft\yii2\base\Controller.php下的runAction函数

  

  ---代码如下:

public function runAction($id, $params = [])
{
    $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;

    // call beforeAction on modules
    foreach ($this->getModules() as $module) {
        if ($module->beforeAction($action)) {    //执行一个事件
            array_unshift($modules, $module);
        } else {
            $runAction = false;
            break;
        }
    }

    $result = null;

    if ($runAction && $this->beforeAction($action)) {
        // run the action
        $result = $action->runWithParams($params);    //执行控制器方法

        $result = $this->afterAction($action, $result);    //执行一个事件

        // call afterAction on modules
        foreach ($modules as $module) {
            /* @var $module Module */
            $result = $module->afterAction($action, $result);
        }
    }

    $this->action = $oldAction;    //还原备份

    return $result;    //返回$result结果
}
---createAction函数代码
public function createAction($id)
{
    if ($id === '') {
        $id = $this->defaultAction;
    }

    $actionMap = $this->actions();
    if (isset($actionMap[$id])) {
        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) {    //通过反射方法类,获取方法在控制器中的信息
                return new InlineAction($id, $this, $methodName);   //内联方法对象
            }
        }
    }

    return null;
}

  ---运行流程总结:

  

 

@备注:

1,yii中get一般是获取对象实例化,set一般是设置属性等。

posted on 2017-01-07 08:55  学到老死  阅读(914)  评论(0)    收藏  举报