Yii2的整体结构概览

Yii2的整体结构概览

一、yii2内部中各类之间的关系

1.  各类之间的关系

yii\web\Application->yii\base\Application->yii\base\Module->yii\di\ServiceLocator->yii\base\Component->yii\base\BaseObject->yii\base\Configurable

SiteController -> yii\web\Controller -> yii\base\Controller -> yii\base\Component->yii\base\BaseObject->yii\base\Configurable

2.  目录以及文件

yii\base目录:框架的底层类

yii\base\Module:  子应用程序,debug、gii都是独立的module

yii\di\ServiceLocator:  服务定位器,主要负责组件component的管理

yii\base\Component:  这个类非常重要,它所实现的属性、事件、行为功能贯穿yii2源码

yii\base\BaseObject:  该类中没有事件行为机制,自己实现的类可以继承该类

二、 预初始化

从入口脚本index.php着手:

我们看到这一段: (new yii\web\Application($config))->run();  启动程序

它实际指的是父类 yii\base\Application::run 方法,是启动整个应用程序的“钥匙”。在执行yii\base\Application::run之前, yii\base\Application 的构造方法__construct会先被执行。

下面截取 yii\base\Application 中的一段代码:

 1 <?php
 2 namespace yii\base;
 3 
 4 use Yii;
 5 abstract class Application extends Module
 6 {
 7     const EVENT_BEFORE_REQUEST = 'beforeRequest';
 8     const EVENT_AFTER_REQUEST = 'afterRequest';
 9     const STATE_BEGIN = 0;
10     const STATE_INIT = 1;
11     const STATE_BEFORE_REQUEST = 2;
12     const STATE_HANDLING_REQUEST = 3;
13     const STATE_AFTER_REQUEST = 4;
14     const STATE_SENDING_RESPONSE = 5;
15     const STATE_END = 6;
16     ...
17     public function __construct($config = [])
18     {
19         Yii::$app = $this;
20         static::setInstance($this);
21 
22         $this->state = self::STATE_BEGIN;
23 
24         $this->preInit($config); // 预初始化
25 
26         $this->registerErrorHandler($config);
27 
28         Component::__construct($config);
29     }
30 }

Component::__construct实际指的是 yii\base\BaseObject::__construct,这个方法的作用是什么呢?

顺便提下:

1) Object是php7.2中的保留类名,不可以使用Object作为类的名称。

2) 从 Yii2 2.0.13版本开始,已经弃用了 Object类,此前版本中的yii\base\Object使用 yii\base\BaseObject 进行代替

接下来,我们来看下yii\base\BaseObject::__construct( )

 1 <?php
 2 namespace yii\base;
 3 
 4 use Yii;
 5 class BaseObject implements Configurable
 6 {
 7     ...
 8     // 该方法有两个重点含义:
 9     // 1.调用 yii\BaseYii::configure 方法,初始化 yii\web\Application 类的属性
10     // 2.调用 yii\web\Application::init 方法完成初始化操作
11     public function __construct($config = [])
12     {
13         if (!empty($config)) {
14             // $this 自然指的是 yii\web\Application,$config 指的是我们的配置数组。
15             Yii::configure($this, $config);
16         }
17         $this->init();
18     }  
19 }

说明:

    yii\base\BaseObject::__construct 方法内执行 $this->init(),也就是说,但凡是继承 yii\base\BaseObject 的类,init方法都是在 __construct 方法运行后调用。这在继承复杂的类时,很重要也很方便。前提是一旦重写父类的 __construct 方法,记得调用 parent::__construct哦。

    这里 $this->init 方法的调用,实际是调用 yii\base\Application::init 方法。该方法则会带入我们进入下一阶段:应用的初始化

    从应用的角度来说,$this 指的就是 yii\web\Application 类的实例 Yii::$app,这里调用的自然就是 yii\web\Application 的init方法,但是在yii\web\Application中并没有找到 init 方法,不过我们在其父类 yii\base\Application 中找

到对应的init方法,代码如下:

 1 <?php
 2 namespace yii\base;
 3 
 4 use Yii;
 5 abstract class Application extends Module
 6 {
 7     ...
 8     public function init()
 9     {
10         $this->state = self::STATE_INIT;
11         $this->bootstrap();
12     }
13 
14 }

 yii\base\Application::bootstrap 方法主要是初始化扩展和执行component 的 bootstrap方法,该方法主要用于一些component的启动工作。

三、执行请求

上面讲的内容其实都是在为运行应用而做的准备,应用的运行,可能要分为几个步骤,我们先从 yii\base\Application::run 方法说起

 1 <?php
 2 namespace yii\base;
 3 
 4 use Yii;
 5 abstract class Application extends Module
 6 {
 7     ...
 8     public function run()
 9     {
10         try {
11             $this->state = self::STATE_BEFORE_REQUEST;
12             $this->trigger(self::EVENT_BEFORE_REQUEST);
13 
14             $this->state = self::STATE_HANDLING_REQUEST;
15             // 非常重要:捕获路由,处理路由以及根据路由规则调用对应的方法
16             $response = $this->handleRequest($this->getRequest());
17 
18             $this->state = self::STATE_AFTER_REQUEST;
19             $this->trigger(self::EVENT_AFTER_REQUEST);
20 
21             $this->state = self::STATE_SENDING_RESPONSE;
22             $response->send();
23 
24             $this->state = self::STATE_END;
25 
26             return $response->exitStatus;
27         } catch (ExitException $e) {
28             $this->end($e->statusCode, isset($response) ? $response : null);
29             return $e->statusCode;
30         }
31     }
32     
33     // 由yii\web\Application实现了该抽象方法
34     abstract public function handleRequest($request);
35 
36 }

精华部分:

yii\web\Application::handleRequest 相关代码如下:

主要分为三部分:

1.  解析路由
2.  运行路由指定的控制器操作
3.  响应客户端请求

 1 <?php
 2 namespace yii\web;
 3 
 4 use Yii;
 5 use yii\base\InvalidRouteException;
 6 use yii\helpers\Url;
 7 
 8 class Application extends \yii\base\Application
 9 {
10     ...
11     public function handleRequest($request)
12     {
13         // 解析路由
14         if (empty($this->catchAll)) {
15             try {
16                 list($route, $params) = $request->resolve();
17             } catch (UrlNormalizerRedirectException $e) {
18                 $url = $e->url;
19                 if (is_array($url)) {
20                     if (isset($url[0])) {
21                         // ensure the route is absolute
22                         $url[0] = '/' . ltrim($url[0], '/');
23                     }
24                     $url += $request->getQueryParams();
25                 }
26 
27                 return $this->getResponse()->redirect(Url::to($url, $e->scheme), $e->statusCode);
28             }
29         } else {
30             $route = $this->catchAll[0];
31             $params = $this->catchAll;
32             unset($params[0]);
33         }
34         try {
35             // 运行路由指定的控制器操作
36             Yii::debug("Route requested: '$route'", __METHOD__);
37             $this->requestedRoute = $route;
38             $result = $this->runAction($route, $params);
39 
40             // 响应客户端请求
41             if ($result instanceof Response) {
42                 return $result;
43             }
44 
45             $response = $this->getResponse();
46             if ($result !== null) {
47                 $response->data = $result;
48             }
49 
50             return $response;
51         } catch (InvalidRouteException $e) {
52             throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'), $e->getCode(), $e);
53         }
54     }
55 }

重点放在第二部分:

yii\base\Module::runAction

分为两部分:
1.   创建controller实例:yii\base\Module::createController
1) 注意里面有一个controllerMap优先的原则
2) yii\base\Module::createControllerByID
3) gii/debug没有被解析到giiController/debugController的原因
4) 控制器必须是SiteController的原因
5) controller都得是yii\base\Controller的子类

yii\base\Module中相关代码如下:

 1 <?php
 2 namespace yii\base;
 3 
 4 use Yii;
 5 use yii\di\ServiceLocator;
 6 
 7 class Module extends ServiceLocator
 8 {
 9     const EVENT_BEFORE_ACTION = 'beforeAction';
10     const EVENT_AFTER_ACTION = 'afterAction';
11     ...
12     public function runAction($route, $params = [])
13     {
14         // 1. 创建controller实例
15         $parts = $this->createController($route);
16         if (is_array($parts)) {
17             /* @var $controller Controller */
18             list($controller, $actionID) = $parts;
19             $oldController = Yii::$app->controller;
20             Yii::$app->controller = $controller;
21             // 运行controller的action
22             $result = $controller->runAction($actionID, $params);
23             if ($oldController !== null) {
24                 Yii::$app->controller = $oldController;
25             }
26 
27             return $result;
28         }
29 
30         $id = $this->getUniqueId();
31         throw new InvalidRouteException('Unable to resolve the request "' . ($id === '' ? $route : $id . '/' . $route) . '".');
32     }
33 
34     ....
35     public function createController($route)
36     {
37         // 访问省略路由的时候,默认就是site
38         if ($route === '') {
39             $route = $this->defaultRoute;
40         }
41         ...  
42 
43         // module and controller map take precedence(controllerMap优先的原则)  
44         if (isset($this->controllerMap[$id])) {
45             $controller = Yii::createObject($this->controllerMap[$id], [$id, $this]);
46             return [$controller, $route];
47         }
48 
49         // 这段代码可以用来解释:在地址栏输入 index.php?r=gii,这个路由就被解析到gii模块了,而不是giiController
50         // 不过像gii这种的module,它既然也是一个独立的module,所以势必最终还是会重新走createController方法,即我们还是可以通过controllerMap映射阻断他的这种解析。
51         $module = $this->getModule($id);
52         if ($module !== null) {
53             return $module->createController($route);
54         }
55 
56         if (($pos = strrpos($route, '/')) !== false) {
57             $id .= '/' . substr($route, 0, $pos);
58             $route = substr($route, $pos + 1);
59         }
60 
61         $controller = $this->createControllerByID($id);
62         if ($controller === null && $route !== '') {
63             $controller = $this->createControllerByID($id . '/' . $route);
64             $route = '';
65         }
66        
67         // 返回yii\base\Controller的实例和操作ID
68         return $controller === null ? false : [$controller, $route];
69     }
70 
71     public function createControllerByID($id)
72     {
73         ...
74         
75         // 这段代码可以解释:类文件不是class Site而是class SiteController
76         $className = preg_replace_callback('%-([a-z0-9_])%i', function ($matches) {
77                 return ucfirst($matches[1]);
78             }, ucfirst($className)) . 'Controller';
79         $className = ltrim($this->controllerNamespace . '\\' . str_replace('/', '\\', $prefix) . $className, '\\');
80         if (strpos($className, '-') !== false || !class_exists($className)) {
81             return null;
82         }
83         
84         // 必须是yii\base\Controller的子类
85         if (is_subclass_of($className, 'yii\base\Controller')) {
86             $controller = Yii::createObject($className, [$id, $this]);
87             return get_class($controller) === $className ? $controller : null;
88         } elseif (YII_DEBUG) {
89             throw new InvalidConfigException('Controller class must extend from \\yii\\base\\Controller.');
90         }
91 
92         return null;
93     }
94 }

2.  运行controller的action:yii\base\Controller::runAction

下面是yii\base\Controller的相关部分代码:

 1 <?php
 2 namespace yii\base;
 3 
 4 use Yii;
 5 use yii\di\Instance;
 6 use yii\di\NotInstantiableException;
 7 
 8 class Controller extends Component implements ViewContextInterface
 9 {
10     const EVENT_BEFORE_ACTION = 'beforeAction';
11     const EVENT_AFTER_ACTION = 'afterAction';
12     ...
13     public function runAction($id, $params = [])
14     {
15         $action = $this->createAction($id);
16         ...
17 
18         // call beforeAction on modules
19         // yii::$app就是一个独立的module,由于这里的 $module 指的是 yii\web\Application,所以 yii\web\Application::beforeAction先被调起
20         // 其含义指的是先发起对应用级别的 beforeAction 事件调用。yii\web\Application::beforeAction 这里指的是 yii\base\Module::beforeAction
21         foreach ($this->getModules() as $module) {
22             if ($module->beforeAction($action)) {
23                 array_unshift($modules, $module);
24             } else {
25                 $runAction = false;
26                 break;
27             }
28         }
29 
30         $result = null;
31         // 确认runAction为true(应用级别的beforeAction事件返回结果都是true)
32         // 再调用 controller 级别的 beforeAction 事件。这里指的是 yii\web\Application::beforeAction方法
33         if ($runAction && $this->beforeAction($action)) {
34             // run the action
35             $result = $action->runWithParams($params);
36             
37             // 将返回结果交给afterAction处理,这是预留的另外一个事件,方便大家使用  
38             $result = $this->afterAction($action, $result);
39 
40             // call afterAction on modules
41             // 继续调用 module的afterAction,处理一些应用级别的afterAction事件
42             foreach ($modules as $module) {
43                 /* @var $module Module */
44                 $result = $module->afterAction($action, $result);
45             }
46         }
47         ...
48     }
49 
50     public function createAction($id)
51     {
52         // 判断actionID,如果是空,则使用默认的action
53         if ($id === '') {
54             $id = $this->defaultAction;
55         }
56         
57         // action的映射关系,跟controller有点像,如果actions方法中有配置,则执行actions方法配置的优先策略
58         $actionMap = $this->actions();
59         if (isset($actionMap[$id])) {
60             return Yii::createObject($actionMap[$id], [$id, $this]);
61         }
62 
63         if (preg_match('/^(?:[a-z0-9_]+-)*[a-z0-9_]+$/', $id)) {
64             $methodName = 'action' . str_replace(' ', '', ucwords(str_replace('-', ' ', $id)));
65             if (method_exists($this, $methodName)) {
66                 // 通过反射,严格要求此方法是public可访问的
67                 $method = new \ReflectionMethod($this, $methodName);
68                 if ($method->isPublic() && $method->getName() === $methodName) {
69                     //  yii\base\InlineAction 的实例
70                     return new InlineAction($id, $this, $methodName);
71                 }
72             }
73         }
74 
75         return null;
76     }
77 }

总结:

1) yii\base\Controller::createAction: 返回yii\base\InlineAction 的实例
2) 同创建controller实例类似,注意里面有一个controllerMap优先的原则
3) yii\base\Module::beforeAction
4)yii\base\InlineAction::runWithParams 代码如下:

 1 <?php
 2 namespace yii\base;
 3 
 4 use Yii;
 5 
 6 class InlineAction extends Action
 7 {
 8     ...
 9     public function runWithParams($params)
10     {
11         $args = $this->controller->bindActionParams($this, $params);
12         Yii::debug('Running action: ' . get_class($this->controller) . '::' . $this->actionMethod . '()', __METHOD__);
13         if (Yii::$app->requestedParams === null) {
14             Yii::$app->requestedParams = $args;
15         }
16         // 调用 controller::action方法
17         return call_user_func_array([$this->controller, $this->actionMethod], $args);
18     }
19 }

 action执行完了之后,这一切的结果,交由yii\base\Module::runAction方法处理,当然,这个方法也只是一个过程,这个方法的调用,源于最初的运行应用的方法 yii\web\Application::handleRequest 方法。

参考链接:

http://www.manks.top/yii2-analysis-prev-init.html

http://www.manks.top/yii2-analysis-run-action.html

posted @ 2021-04-19 15:39  欢乐豆123  阅读(386)  评论(0编辑  收藏  举报