Yii2的整体结构概览
一、yii2内部中各类之间的关系
1. 各类之间的关系
yiiwebApplication->yiiaseApplication->yiiaseModule->yiidiServiceLocator->yiiaseComponent->yiiaseBaseObject->yiiaseConfigurable
SiteController -> yiiwebController -> yiiaseController -> yiiaseComponent->yiiaseBaseObject->yiiaseConfigurable
2. 目录以及文件
yiiase目录:框架的底层类
yiiaseModule: 子应用程序,debug、gii都是独立的module
yiidiServiceLocator: 服务定位器,主要负责组件component的管理
yiiaseComponent: 这个类非常重要,它所实现的属性、事件、行为功能贯穿yii2源码
yiiaseBaseObject: 该类中没有事件行为机制,自己实现的类可以继承该类
二、 预初始化
从入口脚本index.php着手:
我们看到这一段: (new yiiwebApplication($config))->run(); 启动程序
它实际指的是父类 yiiaseApplication::run 方法,是启动整个应用程序的“钥匙”。在执行yiiaseApplication::run之前, yiiaseApplication 的构造方法__construct会先被执行。
下面截取 yiiaseApplication 中的一段代码:
1 <?php 2 namespace yiiase; 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实际指的是 yiiaseBaseObject::__construct,这个方法的作用是什么呢?
顺便提下:
1) Object是php7.2中的保留类名,不可以使用Object作为类的名称。
2) 从 Yii2 2.0.13版本开始,已经弃用了 Object类,此前版本中的yiiaseObject使用 yiiaseBaseObject 进行代替
接下来,我们来看下yiiaseBaseObject::__construct( )
1 <?php 2 namespace yiiase; 3 4 use Yii; 5 class BaseObject implements Configurable 6 { 7 ... 8 // 该方法有两个重点含义: 9 // 1.调用 yiiBaseYii::configure 方法,初始化 yiiwebApplication 类的属性 10 // 2.调用 yiiwebApplication::init 方法完成初始化操作 11 public function __construct($config = []) 12 { 13 if (!empty($config)) { 14 // $this 自然指的是 yiiwebApplication,$config 指的是我们的配置数组。 15 Yii::configure($this, $config); 16 } 17 $this->init(); 18 } 19 }
说明:
yiiaseBaseObject::__construct 方法内执行 $this->init(),也就是说,但凡是继承 yiiaseBaseObject 的类,init方法都是在 __construct 方法运行后调用。这在继承复杂的类时,很重要也很方便。前提是一旦重写父类的 __construct 方法,记得调用 parent::__construct哦。
这里 $this->init 方法的调用,实际是调用 yiiaseApplication::init 方法。该方法则会带入我们进入下一阶段:应用的初始化。
从应用的角度来说,$this 指的就是 yiiwebApplication 类的实例 Yii::$app,这里调用的自然就是 yiiwebApplication 的init方法,但是在yiiwebApplication中并没有找到 init 方法,不过我们在其父类 yiiaseApplication 中找
到对应的init方法,代码如下:
1 <?php 2 namespace yiiase; 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 }
yiiaseApplication::bootstrap 方法主要是初始化扩展和执行component 的 bootstrap方法,该方法主要用于一些component的启动工作。
三、执行请求
上面讲的内容其实都是在为运行应用而做的准备,应用的运行,可能要分为几个步骤,我们先从 yiiaseApplication::run 方法说起
1 <?php 2 namespace yiiase; 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 // 由yiiwebApplication实现了该抽象方法 34 abstract public function handleRequest($request); 35 36 }
精华部分:
yiiwebApplication::handleRequest 相关代码如下:
主要分为三部分:
1. 解析路由
2. 运行路由指定的控制器操作
3. 响应客户端请求
1 <?php 2 namespace yiiweb; 3 4 use Yii; 5 use yiiaseInvalidRouteException; 6 use yiihelpersUrl; 7 8 class Application extends yiiaseApplication 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 }
重点放在第二部分:
yiiaseModule::runAction
分为两部分:
1. 创建controller实例:yiiaseModule::createController
1) 注意里面有一个controllerMap优先的原则
2) yiiaseModule::createControllerByID
3) gii/debug没有被解析到giiController/debugController的原因
4) 控制器必须是SiteController的原因
5) controller都得是yiiaseController的子类
yiiaseModule中相关代码如下:
1 <?php 2 namespace yiiase; 3 4 use Yii; 5 use yiidiServiceLocator; 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 // 返回yiiaseController的实例和操作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 // 必须是yiiaseController的子类 85 if (is_subclass_of($className, 'yiiaseController')) { 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:yiiaseController::runAction
下面是yiiaseController的相关部分代码:
1 <?php 2 namespace yiiase; 3 4 use Yii; 5 use yiidiInstance; 6 use yiidiNotInstantiableException; 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 指的是 yiiwebApplication,所以 yiiwebApplication::beforeAction先被调起 20 // 其含义指的是先发起对应用级别的 beforeAction 事件调用。yiiwebApplication::beforeAction 这里指的是 yiiaseModule::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 事件。这里指的是 yiiwebApplication::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 // yiiaseInlineAction 的实例 70 return new InlineAction($id, $this, $methodName); 71 } 72 } 73 } 74 75 return null; 76 } 77 }
总结:
1) yiiaseController::createAction: 返回yiiaseInlineAction 的实例
2) 同创建controller实例类似,注意里面有一个controllerMap优先的原则
3) yiiaseModule::beforeAction
4)yiiaseInlineAction::runWithParams 代码如下:
1 <?php 2 namespace yiiase; 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执行完了之后,这一切的结果,交由yiiaseModule::runAction方法处理,当然,这个方法也只是一个过程,这个方法的调用,源于最初的运行应用的方法 yiiwebApplication::handleRequest 方法。
参考链接:
http://www.manks.top/yii2-analysis-prev-init.html
http://www.manks.top/yii2-analysis-run-action.html