前言
本文主要分析Yii2應(yīng)用的啟動(dòng)、運(yùn)行的過程畏腕,主要包括以下三部分:入口腳本鸥拧、啟動(dòng)應(yīng)用、運(yùn)行應(yīng)用累榜。
入口腳本
<?php
// 定義全局變量
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');
// composer自動(dòng)加載代碼機(jī)制营勤,可參考 https://segmentfault.com/a/1190000010788354
require __DIR__ . '/../vendor/autoload.php';
// 1.引入工具類Yii
// 2.注冊(cè)自動(dòng)加載函數(shù)
// 3.生成依賴注入中使用到的容器
require __DIR__ . '/../vendor/yiisoft/yii2/Yii.php';
// 加載應(yīng)用配置文件
$config = require __DIR__ . '/../config/web.php';
//生成應(yīng)用實(shí)例并運(yùn)行
(new yii\web\Application($config))->run();
啟動(dòng)應(yīng)用
1.分析:new yii\web\Application($config)
主要就是執(zhí)行父類構(gòu)造函數(shù)(代碼位置:vendor\yiisoft\yii2\base\Application.php)
public function __construct($config = [])
{
// 將Yii::$app 保存為當(dāng)前對(duì)象
Yii::$app = $this;
// 見分析2
static::setInstance($this);
// 設(shè)置當(dāng)前應(yīng)用狀態(tài)
$this->state = self::STATE_BEGIN;
// 進(jìn)行一些預(yù)處理(根據(jù)$config配置應(yīng)用)
// 詳細(xì)代碼位置:yii\base\Application::preInit(&$config) 見分析3
$this->preInit($config);
//注冊(cè)ErrorHandler灵嫌,這樣一旦拋出了異常或錯(cuò)誤葛作,就會(huì)由其負(fù)責(zé)處理
//代碼位置:yii\base\Application::registerErrorHandler(&$config) 見分析4
$this->registerErrorHandler($config);
// 根據(jù)$config配置Application
// 然后執(zhí)行yii\web\Application::init()(實(shí)際上是執(zhí)行yii\base\Application::init())見分析5
Component::__construct($config);
}
2.分析:yii\base\Module::setInstance($instance)
/**
* Sets the currently requested instance of this module class.
* @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)
{
if ($instance === null) {
unset(Yii::$app->loadedModules[get_called_class()]);
} else {
// 將Application實(shí)例本身記錄為“已加載模塊”
Yii::$app->loadedModules[get_class($instance)] = $instance;
}
}
3.分析:yii\base\Application::preInit(&$config)
/**
* Pre-initializes the application.
* This method is called at the beginning of the application constructor.
* It initializes several important application properties.
* If you override this method, please make sure you call the parent implementation.
* @param array $config the application configuration
* @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing.
*/
public function preInit(&$config)
{
// 1.判斷$config中是否有配置項(xiàng)‘id’寿羞,‘basePath’(必須有,否則拋異常)
if (!isset($config['id'])) {
throw new InvalidConfigException('The "id" configuration for the Application is required.');
}
if (isset($config['basePath'])) {
$this->setBasePath($config['basePath']);
unset($config['basePath']);
} else {
throw new InvalidConfigException('The "basePath" configuration for the Application is required.');
}
// 2.設(shè)置別名:@vendor,@runtime
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();
}
// 3.設(shè)置時(shí)間區(qū)域(timeZone)
if (isset($config['timeZone'])) {
$this->setTimeZone($config['timeZone']);
unset($config['timeZone']);
} elseif (!ini_get('date.timezone')) {
$this->setTimeZone('UTC');
}
// 4.自定義配置容器(Yii::$container)的屬性(由這里我們知道可以自定義配置容器)
if (isset($config['container'])) {
$this->setContainer($config['container']);
unset($config['container']);
}
// 5.合并核心組件配置到自定義組件配置:數(shù)組$config['components']
//(核心組件有哪些參考:yii\web\Application::coreComponents())
//(注意:這個(gè)方法中$config使用了引用赂蠢,所以合并$config['components']可以改變$config原來的值)
// merge core components with custom components
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'];
}
}
}
4.分析:yii\base\Application::registerErrorHandler(&$config)
/**
* Registers the errorHandler component as a PHP error handler.
* @param array $config application config
*/
protected function registerErrorHandler(&$config)
{
if (YII_ENABLE_ERROR_HANDLER) {
if (!isset($config['components']['errorHandler']['class'])) {
echo "Error: no errorHandler component is configured.\n";
exit(1);
}
$this->set('errorHandler', $config['components']['errorHandler']);
unset($config['components']['errorHandler']);
$this->getErrorHandler()->register();
}
}
// 默認(rèn)config文件關(guān)于error handler的配置绪穆,這里的配置會(huì)合并到Y(jié)ii2本身對(duì)核心組件的定義中去(),核心組件的定義在yii\web\Application::coreComponents()
'components' => [
'errorHandler' => [
'errorAction' => 'site/error',
],
],
// yii\base\ErrorHandler::register()
/**
* Register this error handler.
* @since 2.0.32 this will not do anything if the error handler was already registered
*/
public function register()
{
if (!$this->_registered) {
// 該選項(xiàng)設(shè)置是否將錯(cuò)誤信息作為輸出的一部分顯示到屏幕虱岂,
// 或者對(duì)用戶隱藏而不顯示玖院。
ini_set('display_errors', false);
// 當(dāng)拋出異常且沒有被catch的時(shí)候,由yii\base\ErrorHandler::handleException()處理
set_exception_handler([$this, 'handleException']);
if (defined('HHVM_VERSION')) {
set_error_handler([$this, 'handleHhvmError']);
} else {
// 當(dāng)出現(xiàn)error的時(shí)候由yii\base\ErrorHandler::handleError()處理
set_error_handler([$this, 'handleError']);
}
if ($this->memoryReserveSize > 0) {
$this->_memoryReserve = str_repeat('x', $this->memoryReserveSize);
}
// 當(dāng)出現(xiàn)fatal error的時(shí)候由yii\base\ErrorHandler::handleFatalError()處理
register_shutdown_function([$this, 'handleFatalError']);
$this->_registered = true;
}
}
一個(gè)值得注意的地方:
在handleException()、handleError()第岖、handleFatalError()中會(huì)直接或間接調(diào)用yii\web\ErrorHandle::renderException(),而在這個(gè)函數(shù)里面难菌,有以下這一行代碼
$result = Yii::$app->runAction($this->errorAction);
回顧上面config文件中對(duì)errorAction這個(gè)屬性的定義,我們知道可以通過自定義一個(gè)action用于在異成馨粒或錯(cuò)誤時(shí)顯示自定義的出錯(cuò)頁面
例如yii2中默認(rèn)使用’site/error’這個(gè)action來處理
5.分析:Component::__construct($config)
(代碼實(shí)際位置:/vendor/yiisoft/yii2/base/BaseObject.php)
/**
* Constructor.
*
* The default implementation does two things:
*
* - 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 = [])
{
if (!empty($config)) {
// 見下面的詳細(xì)分析
Yii::configure($this, $config);
}
// 跳轉(zhuǎn)到y(tǒng)ii\base\Application::init()扔傅,見下面的分析
$this->init();
}
/**
* Configures an object with the initial property values.
* @param object $object the object to be configured
* @param array $properties the property initial values given in terms of name-value pairs.
* @return object the object itself
*/
//Yii::configure($object, $properties)分析
//實(shí)際上就是為Application的屬性賦值
//注意:這里會(huì)觸發(fā)一些魔術(shù)方法,間接調(diào)用了:
//yii\web\Application::setHomeUrl()
//yii\di\ServiceLocator::setComponents()(這個(gè)值得注意耍共,詳細(xì)參考本文后續(xù)“5.1分析”)
//yii\base\Module::setModules()
public static function configure($object, $properties)
{
foreach ($properties as $name => $value) {
$object->$name = $value;
}
return $object;
}
/**
* {@inheritdoc}
*/
// yii\base\Application::init()分析
public function init()
{
// 設(shè)置當(dāng)前應(yīng)用狀態(tài)
$this->state = self::STATE_INIT;
// 見下面的bootstrap()方法
$this->bootstrap();
}
/**
* Initializes extensions and executes bootstrap components.
* This method is called by [[init()]] after the application has been fully configured.
* If you override this method, make sure you also call the parent implementation.
*/
protected function bootstrap()
{
if ($this->extensions === null) {
// @vendor/yiisoft/extensions.php是一些關(guān)于第三方擴(kuò)展的配置烫饼,當(dāng)用composer require安裝第三擴(kuò)展的時(shí)候就會(huì)將新的擴(kuò)展的相關(guān)信息記錄到該文件,這樣我們就可以在代碼中調(diào)用了
$file = Yii::getAlias('@vendor/yiisoft/extensions.php');
$this->extensions = is_file($file) ? include $file : [];
}
foreach ($this->extensions as $extension) {
if (!empty($extension['alias'])) {
foreach ($extension['alias'] as $name => $path) {
Yii::setAlias($name, $path);
}
}
// 進(jìn)行一些必要的預(yù)啟動(dòng)
if (isset($extension['bootstrap'])) {
$component = Yii::createObject($extension['bootstrap']);
if ($component instanceof BootstrapInterface) {
Yii::debug('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);
$component->bootstrap($this);
} else {
Yii::debug('Bootstrap with ' . get_class($component), __METHOD__);
}
}
}
// 預(yù)啟動(dòng)试读,通常會(huì)包括‘log’組件杠纵,‘debug’模塊和‘gii’模塊(參考配置文件)
foreach ($this->bootstrap as $mixed) {
$component = null;
if ($mixed instanceof \Closure) {
Yii::debug('Bootstrap with Closure', __METHOD__);
if (!$component = call_user_func($mixed, $this)) {
continue;
}
} elseif (is_string($mixed)) {
if ($this->has($mixed)) {
$component = $this->get($mixed);
} elseif ($this->hasModule($mixed)) {
$component = $this->getModule($mixed);
} elseif (strpos($mixed, '\\') === false) {
throw new InvalidConfigException("Unknown bootstrapping component ID: $mixed");
}
}
if (!isset($component)) {
$component = Yii::createObject($mixed);
}
if ($component instanceof BootstrapInterface) {
Yii::debug('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);
$component->bootstrap($this);
} else {
Yii::debug('Bootstrap with ' . get_class($component), __METHOD__);
}
}
}
在完成了上面的流程之后,應(yīng)用就算啟動(dòng)成功了钩骇,可以開始運(yùn)行比藻,處理請(qǐng)求了。
運(yùn)行應(yīng)用
1.分析:yii\base\Application::run()
在上面的構(gòu)造函數(shù)執(zhí)行完后倘屹,開始運(yùn)行應(yīng)用银亲。即下面這行代碼的run()部分
(new yii\web\Application($config))->run();//(實(shí)際上執(zhí)行的是yii\base\Application::run())
/**
* Runs the application.
* 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;
// 觸發(fā)事件’beforeRequest’,依次執(zhí)行該事件的handler,
$this->trigger(self::EVENT_BEFORE_REQUEST);
$this->state = self::STATE_HANDLING_REQUEST;
//處理請(qǐng)求纽匙,這里的返回值是yii\web\Response實(shí)例
//handleRequest(),詳細(xì)參考本文后續(xù)“分析2”
$response = $this->handleRequest($this->getRequest());
$this->state = self::STATE_AFTER_REQUEST;
// 觸發(fā)事件’afterRequest’,依次執(zhí)行該事件的handler
$this->trigger(self::EVENT_AFTER_REQUEST);
$this->state = self::STATE_SENDING_RESPONSE;
// 發(fā)送響應(yīng),詳細(xì)參考本文后續(xù)“分析3”
$response->send();
$this->state = self::STATE_END;
return $response->exitStatus;
} catch (ExitException $e) {
// 結(jié)束運(yùn)行
$this->end($e->statusCode, isset($response) ? $response : null);
return $e->statusCode;
}
}
2.分析: yii\web\Application::handleRequest()
/**
* 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)) {
try {
// 從請(qǐng)求中解析路由和參數(shù)务蝠,這里會(huì)調(diào)用urlManager組件來處理
list($route, $params) = $request->resolve();
} catch (UrlNormalizerRedirectException $e) {
$url = $e->url;
if (is_array($url)) {
if (isset($url[0])) {
// ensure the route is absolute
$url[0] = '/' . ltrim($url[0], '/');
}
$url += $request->getQueryParams();
}
// 當(dāng)解析請(qǐng)求出現(xiàn)異常時(shí)進(jìn)行重定向
return $this->getResponse()->redirect(Url::to($url, $e->scheme), $e->statusCode);
}
} else {
// ’catchAll’參數(shù)可以在配置文件中自定義,可用于在項(xiàng)目需要臨時(shí)下線維護(hù)時(shí)給出一個(gè)統(tǒng)一的訪問路由
$route = $this->catchAll[0];
$params = $this->catchAll;
unset($params[0]);
}
try {
Yii::debug("Route requested: '$route'", __METHOD__);
// 記錄下當(dāng)前請(qǐng)求的route
$this->requestedRoute = $route;
// 執(zhí)行路由相對(duì)應(yīng)的action(yii\base\Module::runAction()詳細(xì)參考本文后續(xù)“分析3”)
$result = $this->runAction($route, $params);
if ($result instanceof Response) {
return $result;
}
// 如果action的返回結(jié)果不是Response的實(shí)例烛缔,則將結(jié)果封裝到Response實(shí)例的data屬性中
$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);
}
}
3.分析: yii\base\Module::runAction()
public function runAction($route, $params = [])
{
//根據(jù)路由創(chuàng)建Controller實(shí)例(詳細(xì)參考本文后續(xù)“分析4”)
$parts = $this->createController($route);
if (is_array($parts)) {
/* @var $controller Controller */
// 解析出控制器和方法
list($controller, $actionID) = $parts;
$oldController = Yii::$app->controller;
// 設(shè)置當(dāng)前的Controller實(shí)例
Yii::$app->controller = $controller;
// 執(zhí)行action(yii\base\Controller::runAction()詳細(xì)參考本文后續(xù)“分析5”)
$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) . '".');
}
4.分析: yii\base\Module::createController()
/**
* Creates a controller instance based on the given route.
*
* The route should be relative to this module. The method implements the following algorithm
* to resolve the given route:
*
* 1. If the route is empty, use [[defaultRoute]];
* 2. If the first segment of the route is found in [[controllerMap]], create a controller
* based on the corresponding configuration found in [[controllerMap]];
* 3. If the first segment of the route is a valid module ID as declared in [[modules]],
* call the module's `createController()` with the rest part of the route;
* 4. The given route is in the format of `abc/def/xyz`. Try either `abc\DefController`
* or `abc\def\XyzController` class within the [[controllerNamespace|controller namespace]].
*
* If any of the above steps resolves into a controller, it is returned together with the rest
* part of the route which will be treated as the action ID. Otherwise, `false` will be returned.
*
* @param string $route the route consisting of module, controller and action IDs.
* @return array|bool If the controller is created successfully, it will be returned together
* with the requested action ID. Otherwise `false` will be returned.
* @throws InvalidConfigException if the controller class and its file do not match.
*/
public function createController($route)
{
// 如果route為空則設(shè)置route為默認(rèn)路由馏段,這個(gè)可以在配置文件中自定義
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 = '';
}
// 優(yōu)先使用controllerMap,controllerMap可以如下
// module and controller map take precedence
if (isset($this->controllerMap[$id])) {
$controller = Yii::createObject($this->controllerMap[$id], [$id, $this]);
return [$controller, $route];
}
// 先判斷是否存在相應(yīng)的模塊
$module = $this->getModule($id);
if ($module !== null) {
// 當(dāng)存在模塊時(shí)践瓷,進(jìn)行遞歸
return $module->createController($route);
}
if (($pos = strrpos($route, '/')) !== false) {
$id .= '/' . substr($route, 0, $pos);
$route = substr($route, $pos + 1);
}
// 最終找到Controller的id
$controller = $this->createControllerByID($id);
if ($controller === null && $route !== '') {
// 詳細(xì)見下面的代碼分析
$controller = $this->createControllerByID($id . '/' . $route);
$route = '';
}
// 返回Controller實(shí)例和剩下的路由信息
return $controller === null ? false : [$controller, $route];
}
/**
* Creates a controller based on the given controller ID.
*
* The controller ID is relative to this module. The controller class
* should be namespaced under [[controllerNamespace]].
*
* Note that this method does not check [[modules]] or [[controllerMap]].
*
* @param string $id the controller ID.
* @return Controller|null the newly created controller instance, or `null` if the controller ID is invalid.
* @throws InvalidConfigException if the controller class and its file name do not match.
* This exception is only thrown when in debug mode.
*/
public function createControllerByID($id)
{
$pos = strrpos($id, '/');
if ($pos === false) {
$prefix = '';
$className = $id;
} else {
$prefix = substr($id, 0, $pos + 1);
$className = substr($id, $pos + 1);
}
if ($this->isIncorrectClassNameOrPrefix($className, $prefix)) {
return null;
}
$className = preg_replace_callback('%-([a-z0-9_])%i', function ($matches) {
return ucfirst($matches[1]);
}, ucfirst($className)) . 'Controller';
$className = ltrim($this->controllerNamespace . '\\' . str_replace('/', '\\', $prefix) . $className, '\\');
if (strpos($className, '-') !== false || !class_exists($className)) {
return null;
}
if (is_subclass_of($className, 'yii\base\Controller')) {
// 通過依賴注入容器獲得Controller實(shí)例
$controller = Yii::createObject($className, [$id, $this]);
return get_class($controller) === $className ? $controller : null;
} elseif (YII_DEBUG) {
throw new InvalidConfigException('Controller class must extend from \\yii\\base\\Controller.');
}
return null;
}
5.分析:yii\base\Controller::runAction()
/**
* Runs an action within this controller with the specified action ID and parameters.
* If the action ID is empty, the method will use [[defaultAction]].
* @param string $id the ID of the action to be executed.
* @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 = [])
{
// 創(chuàng)建action實(shí)例院喜,詳細(xì)見下面的代碼
$action = $this->createAction($id);
if ($action === null) {
throw new InvalidRouteException('Unable to resolve the request: ' . $this->getUniqueId() . '/' . $id);
}
Yii::debug('Route to run: ' . $action->getUniqueId(), __METHOD__);
if (Yii::$app->requestedAction === null) {
Yii::$app->requestedAction = $action;
}
$oldAction = $this->action;
$this->action = $action;
$modules = [];
$runAction = true;
// 返回的modules包括該controller當(dāng)前所在的module,以及該module的所有祖先module(遞歸直至沒有祖先module)
// 然后從最初的祖先module開始晕翠,依次執(zhí)行“模塊級(jí)”的beforeAction()
// 如果有beforeAction()沒有返回true喷舀, 那么會(huì)中斷后續(xù)的執(zhí)行
// call beforeAction on modules
foreach ($this->getModules() as $module) {
if ($module->beforeAction($action)) {
array_unshift($modules, $module);
} else {
$runAction = false;
break;
}
}
$result = null;
// 執(zhí)行當(dāng)前控制器的beforeAction,通過后再最終執(zhí)行action
//(如果前面“模塊級(jí)beforeAction”沒有全部返回true,則這里不會(huì)執(zhí)行)
if ($runAction && $this->beforeAction($action)) {
// run the action
//代碼位置:yii\base\InlineAction::runWithParams()詳細(xì)參考本文后續(xù)
$result = $action->runWithParams($params);
// 執(zhí)行當(dāng)前Controller的afterAction
$result = $this->afterAction($action, $result);
// call afterAction on modules
// 從當(dāng)前模塊開始元咙,執(zhí)行afterAction梯影,直至所有祖先的afterAction
foreach ($modules as $module) {
/* @var $module Module */
$result = $module->afterAction($action, $result);
}
}
if ($oldAction !== null) {
$this->action = $oldAction;
}
// 如果有beforeAction沒有通過,那么會(huì)返回null
return $result;
}
/**
* Creates an action based on the given action ID.
* The method first checks if the action ID has been declared in [[actions()]]. If so,
* it will use the configuration declared there to create the action object.
* If not, it will look for a controller method whose name is in the format of `actionXyz`
* where `xyz` is the action ID. If found, an [[InlineAction]] representing that
* method will be created and returned.
* @param string $id the action ID.
* @return Action|null the newly created action instance. Null if the ID doesn't resolve into any action.
*/
// 根據(jù)給定的動(dòng)作ID創(chuàng)建一個(gè)動(dòng)作庶香。該方法首先檢查動(dòng)作ID是否已在[[actions()]]中聲明甲棍。如果是這樣,它將使用在那里聲明的配置來創(chuàng)建操作對(duì)象赶掖。如果不是感猛,它將尋找名稱為actionXyz格式的控制器方法,其中xyz是動(dòng)作ID奢赂。如果找到陪白,將創(chuàng)建并返回表示該方法的[[InlineAction]]。
public function createAction($id)
{
if ($id === '') {
$id = $this->defaultAction;
}
$actionMap = $this->actions();
if (isset($actionMap[$id])) {
return Yii::createObject($actionMap[$id], [$id, $this]);
}
if (preg_match('/^(?:[a-z0-9_]+-)*[a-z0-9_]+$/', $id)) {
$methodName = 'action' . str_replace(' ', '', ucwords(str_replace('-', ' ', $id)));
if (method_exists($this, $methodName)) {
$method = new \ReflectionMethod($this, $methodName);
if ($method->isPublic() && $method->getName() === $methodName) {
// InlineAction封裝了將要執(zhí)行的action的相關(guān)信息膳灶,該類繼承自yii\base\Action
return new InlineAction($id, $this, $methodName);
}
}
}
return null;
}
6.分析: yii\web\Response::send()
/**
* Sends the response to the client.
*/
public function send()
{
if ($this->isSent) {
return;
}
// 觸發(fā)事件
$this->trigger(self::EVENT_BEFORE_SEND);
// 預(yù)處理
$this->prepare();
// 觸發(fā)事件
$this->trigger(self::EVENT_AFTER_PREPARE);
// 發(fā)送http響應(yīng)的頭部咱士,詳見下面的代碼
$this->sendHeaders();
// 發(fā)送http響應(yīng)的主體,詳見下面的代碼
$this->sendContent();
// 觸發(fā)事件
$this->trigger(self::EVENT_AFTER_SEND);
$this->isSent = true;
}
請(qǐng)求生命周期
- 用戶向入口腳本
web/index.php
發(fā)起請(qǐng)求轧钓。 - 入口腳本加載應(yīng)用配置并創(chuàng)建一個(gè)應(yīng)用 實(shí)例去處理請(qǐng)求序厉。
- 應(yīng)用通過請(qǐng)求組件解析請(qǐng)求的 路由。
- 應(yīng)用創(chuàng)建一個(gè)控制器實(shí)例去處理請(qǐng)求毕箍。
- 控制器創(chuàng)建一個(gè)動(dòng)作實(shí)例并針對(duì)操作執(zhí)行過濾器弛房。
- 如果任何一個(gè)過濾器返回失敗,則動(dòng)作取消而柑。
- 如果所有過濾器都通過文捶,動(dòng)作將被執(zhí)行。
- 動(dòng)作會(huì)加載一個(gè)數(shù)據(jù)模型媒咳,或許是來自數(shù)據(jù)庫粹排。
- 動(dòng)作會(huì)渲染一個(gè)視圖,把數(shù)據(jù)模型提供給它涩澡。
- 渲染結(jié)果返回給響應(yīng)組件顽耳。
- 響應(yīng)組件發(fā)送渲染結(jié)果給用戶瀏覽器。
靜態(tài)結(jié)構(gòu)
每個(gè)應(yīng)用都有一個(gè)入口腳本 web/index.php
筏养,這是整個(gè)應(yīng)用中唯一可以訪問的 PHP 腳本斧抱。 入口腳本接受一個(gè) Web 請(qǐng)求并創(chuàng)建應(yīng)用實(shí)例去處理它。 應(yīng)用在它的組件輔助下解析請(qǐng)求渐溶, 并分派請(qǐng)求至 MVC 元素辉浦。視圖使用小部件 去創(chuàng)建復(fù)雜和動(dòng)態(tài)的用戶界面。