Yii2请求处理流程:
首先:项目路径/web/index.php
(new yii\web\Application($config))->run();//根据配置文件创建App实例,先实例化yii\web\Application(),然后调用run()方法
该语句可分为两步:
$application = new yii\web\Application($config);//实例化app $application->run();//调用run()方法
$config 为配置文件,通过 require引入:
$config = require(__DIR__ . '/../config/web.php');//引用配置文件
首先看实例化的过程:
$application = new yii\web\Application($config);//分析完成
顺着命名空间可以发现,实例化的类为yii\web下的Application()类:
class Application extends \yii\base\Application//继承了yiii\base\Application
yii\web\Application.php中没有使用$config 构造函数,所以我们在它的父类也就是\yii\base\Application查找,在父类中找到了构造函数,如下:
abstract class Application extends Module /** * Constructor. * 实例化应用的构造函数 * @param array $config name-value pairs that will be used to initialize the object properties. * Note that the configuration must contain both [[id]] and [[basePath]]. * @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing. */ public function __construct($config = []) { Yii::$app = $this;//将\yii\base\Application里所有的公共方法都交给了,Yii::$app,其实Yii大部分信息都在Yii::$app变量中 //当然也包括它的父类如:\yii\base\Module \yii\di\ServiceLocator \yii\base\Component \yii\base\Object $this->setInstance($this);//这一句是指向\yii\base\Module,将\yii\base\Application中的所有的属性和方法交给Yii::$app->loadedModules数组中 $this->state = self::STATE_BEGIN;//标记状态 $this->preInit($config);//加载配置文件的框架信息 如:设置别名,设置框架路径等等 最为重要的是给加载默认组件 $this->registerErrorHandler($config);//加载配置文件中的异常处理组件 Component::__construct($config);//将配置文件中的所有信息赋值给Object,也就是Yii::$app->配置文件参数可以直接调用配置文件的内容 如:Yii::$app->vendorPath//输出框架路径 Yii::$app->components['redis']//输出redis配置信息 }
下面分析源代码:
Yii::$app = $this;
这一句的意思是,将\yii\base\Application里所有的公共方法都交给了,Yii::$app,其实Yii大部分信息都在Yii::$app变量中,当然也包括它的父类如:\yii\base\Module \yii\di\ServiceLocator \yii\base\Component \yii\base\Object
$this->setInstance($this);//该句调用的是module中的方法
这一句调用的是\yii\base\Module中的方法:
class Module extends ServiceLocator /** * Sets the currently requested instance of this module class. * 将当前类名和存储类的对象变量加入Yii::$app->loadedModules['yii\web\Application']数组中 * 这样直接通过Yii::$app->loadedModules['yii\web\Application']就可以直接调用这个类 * @param Module|null $instance the currently requested instance of this module class. * If it is null, the instance of the calling class will be removed, if any. */ public static function setInstance($instance)//module模块里会用到,为getInstance提供数据 { if ($instance === null) { unset(Yii::$app->loadedModules[get_called_class()]);//如果没有传入参数,说明是删除,直接unset } else { Yii::$app->loadedModules[get_class($instance)] = $instance;//否则将该类和类的实例存入loadedModules数组中 } }
这句意思是:将当前类名和存储类的对象变量加入Yii::$app->loadedModules['yii\web\Application']数组中这样直接通过Yii::$app->loadedModules['yii\web\Application']就可以直接调用这个类
$this->state = self::STATE_BEGIN;//标记状态
这一句用于标记状态
$this->preInit($config);//加载配置文件的框架信息 如:设置别名,设置框架路径等等 最为重要的是给加载默认组件
这一句是将配置文件中的一些变量设置别名,主要是针对路径、URL之类的
$this->registerErrorHandler($config);//加载配置文件中的异常处理组件
这一句是用于加载异常处理程序的
Component::__construct($config);//将配置文件中的所有信息赋值给Object,也就是Yii::$app->配置文件参数可以直接调用配置文件的内容 如:Yii::$app->vendorPath//输出框架路径 Yii::$app->components['redis']//输出redis配置信息
这一句通过多次继承,最终指向了Object
/** * Constructor. * The default implementation does two things: * 构造方法实现了接口Configurable,通过传入的配置初始化对象,调用init()方法 * - Initializes the object with the given configuration `$config`. * - Call [[init()]]. * * If this method is overridden in a child class, it is recommended that * 如果构造函数在子类中重写,必须调用父类的方法,且最后一个参数为配置数组 * - the last parameter of the constructor is a configuration array, like `$config` here. * - call the parent implementation at the end of the constructor. * * @param array $config name-value pairs that will be used to initialize the object properties */ public function __construct($config = []) { // 根据 $config 内容初始化该对象 if (!empty($config)) { Yii::configure($this, $config);//将配置文件里面的所有配置信息赋值给Object,由于Object是大部分类的基类,实际上也就是交给了yii\web\Application // 可以Yii::$app->配置参数来访问配置文件中的内容 } // 调用 init() 方法,继承该类的类可以重写 init 方法,用于初始化 $this->init(); }
该方法的作用就是把当前的配置文件$config变量中内容交给Object 类,Object类是基础类,所以绝大部分类都能直接调用配置文件中配置内容
然后执行
$this->init();
在请求过程中,这句实际上执行的是yii\base\Module.php中的init()
/** * Initializes the module. * 初始化模块,取出控制器的命名空间,也可以理解为路径 注:第一次加载的时候(即controllerNamespace为null时)才会执行 * This method is called after the module is created and initialized with property values * given in configuration. The default implementation will initialize [[controllerNamespace]] * if it is not set. * * If you override this method, please make sure you call the parent implementation. */ public function init() { if ($this->controllerNamespace === null) {//判断controllerNamespace属性是否被赋值,没有赋值才执行 $class = get_class($this);//获取类名 if (($pos = strrpos($class, '\\')) !== false) { $this->controllerNamespace = substr($class, 0, $pos) . '\\controllers';//取得命名空间 } } }
初始化模块,取出控制器的命名空间,也可以理解为路径 *注:第一次加载的时候才会执行
接着看调用run()的过程:
$application->run();//调用run()方法
该语句指向的是\yii\base\Application.php中的run()方法
/** * Runs the application. * 运行应用,该方法是应用的主入口 * This is the main entrance of an application. * @return integer 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);//加载事件函数beforRequest函数 $this->state = self::STATE_HANDLING_REQUEST; $response = $this->handleRequest($this->getRequest());//加载控制器 //$this->getRequest() //获取Request对象 $this->state = self::STATE_AFTER_REQUEST; $this->trigger(self::EVENT_AFTER_REQUEST);//加载afterRequest事件函数 $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; } }
分析该方法中的关键部分:
$this->getRequest() /** * Returns the request component. * 返回请求的组件对象 * @return \yii\web\Request|\yii\console\Request the request component. */ public function getRequest() { return $this->get('request'); }
该语句调用内部的方法,获取Request对象
$response = $this->handleRequest($this->getRequest());//加载控制器 //$this->getRequest() //获取Request对象
通过调用抽象方法,指向\yii\web\Application.php
/** * Handles the specified request. * 处理指定的请求 * @param Request $request the request to be handled * @return Response the resulting response * @throws NotFoundHttpException if the requested route is invalid */ public function handleRequest($request) { if (empty($this->catchAll)) { 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);//运行模块中的runAcition方法,实际是指向控制器中的Action if ($result instanceof Response) { return $result; } else { /** *这个是加载yii\base\Response类,在外部可以Yii::$app->get('response')、Yii::$app->getResponse()、Yii::$app->response 等等方式来加载response类 *主要用来加载http状态,及头信息,如301,302,404,ajax头等等的获取 */ $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); } }
$result = $this->runAction($route, $params);//运行模块中的runAcition方法,实际是指向控制器中的Action
该语句指向\yii\base\Module.php中
/** * Runs a controller action specified by a route. * 运行路由中指定的控制器方法 * This method parses the specified route and creates the corresponding child module(s), controller and action * instances. It then calls [[Controller::runAction()]] to run the action with the given parameters. * If the route is empty, the method will use [[defaultRoute]]. * @param string $route the route that specifies the action. * @param array $params the parameters to be passed to the action * @return mixed the result of the action. * @throws InvalidRouteException if the requested route cannot be resolved into an action successfully */ public function runAction($route, $params = []) { $parts = $this->createController($route);//根据路由创建控制器对象 if (is_array($parts)) { /* @var $controller Controller */ list($controller, $actionID) = $parts;//获得$actionId和$controller $oldController = Yii::$app->controller; Yii::$app->controller = $controller; $result = $controller->runAction($actionID, $params);//运行使用控制器加载 action方法 Yii::$app->controller = $oldController;//将对象交给Yii::$app->controller 这里面起的作用应该是运行控制器,最后释放控制器的对象变量 return $result; } else { $id = $this->getUniqueId(); throw new InvalidRouteException('Unable to resolve the request "' . ($id === '' ? $route : $id . '/' . $route) . '".'); } }
其中
$parts = $this->createController($route);//根据路由创建控制器对象
创建了控制器对象
$result = $controller->runAction($actionID, $params);//运行使用控制器加载 action方法
用来调用runAction()方法加载Action方法
该句指向了\yii\base\Controller.php
/** * Runs an action within this controller with the specified action ID and parameters. * If the action ID is empty, the method will use [[defaultAction]]. * $id 为action的id,也就是操作的名称,如定义的actionIndex,那么id就为Index。 * 如果没有定义 action ID,就会调用默认的操作,例如常用的index * * @param string $id 操作id,也就是操作名 * @param array $params the parameters (name-value pairs) to be passed to the action. * @return mixed the result of the action. * @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully. * @see createAction() */ public function runAction($id, $params = []) { //创建action $action = $this->createAction($id); if ($action === null) { //创建action失败,抛出异常 throw new InvalidRouteException('Unable to resolve the request: ' . $this->getUniqueId() . '/' . $id); } //写入trace信息 Yii::trace('Route to run: ' . $action->getUniqueId(), __METHOD__); if (Yii::$app->requestedAction === null) { Yii::$app->requestedAction = $action;//不知道这个是干嘛用的0.0 } $oldAction = $this->action;//将action中的信息保存到oldAction $this->action = $action; //将当前的action写入action属性中 //用来保存当前控制器的所有父模块,顺序为由子模块到父模块 $modules = []; $runAction = true; /* * 获取当前控制器的所有的模块,并执行每个模块的beforeAction来检查当前的action是否可以执行, * 注意:getModules返回的数组顺序为:从父模块到子模块, * 所以在执行beforeAction的时候,先检查最外层的父模块,然后检查子模块。 * * 然而在执行afterAction的时候,顺序就反过来了,先执行子模块,最后执行父模块。 * 加载默认模块如:Application log等。再调用模块内的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)) {//判断当前控制器中beforeAction,执行beforeAction // 由生成的action对象来执行runWithParams方法 $result = $action->runWithParams($params);//执行控制器里的action //执行完后,再执行afterAction方法 $result = $this->afterAction($action, $result); //执行所有父模块的afterAction foreach ($modules as $module) { /* @var $module Module */ $result = $module->afterAction($action, $result); } } $this->action = $oldAction; //有什么用呢?,看完后面的在回头看吧 return $result; }
这一句才是真正执行action的地方
$result = $action->runWithParams($params);//执行控制器里的action
该语句指向yii\base\InlineAction
/** * Runs this action with the specified parameters. * 使用指定的参数运行action * This method is mainly invoked by the controller. * @param array $params action parameters * @return mixed the result of the action */ public function runWithParams($params) { $args = $this->controller->bindActionParams($this, $params);//对action的参数进行分析,并且赋值给控制器 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); }