您好,登錄后才能下訂單哦!
用yii2框架用了將近2年,一直都沒有去看過它底層源碼, 馬上快不用了,最近對(duì)其源碼研究一番,哈哈
廢話少說,上代碼,
入口文件是web/index.php
<?php defined('YII_DEBUG') or define('YII_DEBUG', true); defined('YII_ENV') or define('YII_ENV', 'dev'); //這行我在composer autoload流程已經(jīng)分析過 require __DIR__ . '/../vendor/autoload.php'; //見解釋1-1 require __DIR__ . '/../vendor/yiisoft/yii2/Yii.php'; //配置文件 $config = require __DIR__ . '/../config/web.php'; //最關(guān)鍵的一點(diǎn),見解釋1-2 (new yii\web\Application($config))->run();
解釋1-1
直接上Yii.php文件源碼
<?php require(__DIR__ . '/BaseYii.php'); class Yii extends \yii\BaseYii { } //實(shí)際上調(diào)用的是BaseYii的autoload方法,自動(dòng)加載yii的類 spl_autoload_register(['Yii', 'autoload'], true, true); //yii類名和yii類名所在文件的映射數(shù)組 Yii::$classMap = require(__DIR__ . '/classes.php'); //依賴注入容器,這個(gè)后續(xù)文章再分析,先知道有這么一個(gè)東東 Yii::$container = new yii\di\Container();
解釋1-2
我們最關(guān)鍵的點(diǎn)來了分析application啟動(dòng)流程
首先看看Application構(gòu)造函數(shù)
首先進(jìn)入yii\web\Application類,發(fā)現(xiàn)沒有構(gòu)造方法,于是跟蹤它的層級(jí)關(guān)系,列出來:
yii\web\Application -> \yii\base\Application -> \yii\base\Module -> \yii\di\ServiceLocator -> \yii\base\Component
-> \yii\base\BaseObject -> \yii\base\Configurable(接口interface)
首先進(jìn)入yii\base\Application找到__construct方法:
public function __construct($config = []) { //保存當(dāng)前啟動(dòng)的application實(shí)例 Yii::$app = $this; //將Yii::$app->loadedModules[實(shí)例類名] = 當(dāng)前實(shí)例; $this->setInstance($this); $this->state = self::STATE_BEGIN; //見解釋1-2-1 $this->preInit($config); //見解釋1-2-2 $this->registerErrorHandler($config); //見解釋1-2-3 Component::__construct($config); }
解釋1-2-1:
/* 該函數(shù)作用是將配置數(shù)組進(jìn)一步合并完善數(shù)組中的key $config即為入口文件包含到的config/web.php返回的數(shù)組,舉例如下: $config = [ 'id' => 'basic', 'basePath' => dirname(__DIR__), 'bootstrap' => ['log'], 'aliases' => [ '@bower' => '@vendor/bower-asset', '@npm' => '@vendor/npm-asset', ], 'components' => [ 'request' => [ // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation 'cookieValidationKey' => '', ], 'cache' => [ 'class' => 'yii\caching\FileCache', ], 'user' => [ 'identityClass' => 'app\models\User', 'enableAutoLogin' => true, ], 'errorHandler' => [ 'errorAction' => 'site/error', ], 'mailer' => [ 'class' => 'yii\swiftmailer\Mailer', 'useFileTransport' => true, ], 'log' => [ 'traceLevel' => YII_DEBUG ? 3 : 0, 'targets' => [ [ 'class' => 'yii\log\FileTarget', 'levels' => ['error', 'warning'], ], ], ], 'db' => $db, ], 'params' => $params, ]; */ public function preInit(&$config) { 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.'); } if (isset($config['vendorPath'])) { $this->setVendorPath($config['vendorPath']); unset($config['vendorPath']); } else { $this->getVendorPath(); } if (isset($config['runtimePath'])) { $this->setRuntimePath($config['runtimePath']); unset($config['runtimePath']); } else { // set "@runtime" $this->getRuntimePath(); } //設(shè)置時(shí)區(qū) if (isset($config['timeZone'])) { $this->setTimeZone($config['timeZone']); unset($config['timeZone']); } elseif (!ini_get('date.timezone')) { $this->setTimeZone('UTC'); } if (isset($config['container'])) { $this->setContainer($config['container']); unset($config['container']); } /* coreComponents返回核心組件 return [ 'log' => ['class' => 'yii\log\Dispatcher'], 'view' => ['class' => 'yii\web\View'], 'formatter' => ['class' => 'yii\i18n\Formatter'], 'i18n' => ['class' => 'yii\i18n\I18N'], 'mailer' => ['class' => 'yii\swiftmailer\Mailer'], 'urlManager' => ['class' => 'yii\web\UrlManager'], 'assetManager' => ['class' => 'yii\web\AssetManager'], 'security' => ['class' => 'yii\base\Security'], ]; 合并配置文件數(shù)組的components key內(nèi)容 */ 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']; } } }
解釋1-2-2:
protected function registerErrorHandler(&$config) { //YII_ENABLE_ERROR_HANDLER可以在文件中配,默認(rèn)為true if (YII_ENABLE_ERROR_HANDLER) { if (!isset($config['components']['errorHandler']['class'])) { echo "Error: no errorHandler component is configured.\n"; exit(1); } /* 曬個(gè)默認(rèn)配置 'errorHandler' => [ 'errorAction' => 'site/error', ], $this->set方法是引自\yii\di\ServiceLocator的set方法, 注冊(cè)組件$this->_definitions['erroHandler'] = ['errorAction' => 'site/error','class'=>'yii\web\ErrorHandler']; */ $this->set('errorHandler', $config['components']['errorHandler']); unset($config['components']['errorHandler']); //這個(gè)方法會(huì)實(shí)例化errorHandler的class,實(shí)例化這步實(shí)際上用到依賴注入,之前我已經(jīng)講過一點(diǎn),以后寫個(gè)yii2創(chuàng)建對(duì)象流程 //并將實(shí)例化的對(duì)象保存到$this->__components['errorHandler'] $this->getErrorHandler()->register(); } }
解釋1-2-3:
//實(shí)際調(diào)用的是yii\base\BaseObject類的構(gòu)造方法 public function __construct($config = []) { if (!empty($config)) { //將$config數(shù)組中的每個(gè)key都賦值$this->本地化變量 Yii::configure($this, $config); } $this->init(); }
很明顯追蹤$this->init()方法,后面追蹤到y(tǒng)ii\base\Application的init方法。
public function init() { $this->state = self::STATE_INIT; $this->bootstrap(); }
再看看bootstrap方法
先看看yii\web\Application的bootstrap方法
protected function bootstrap() { //獲得request對(duì)象實(shí)例 $request = $this->getRequest(); Yii::setAlias('@webroot', dirname($request->getScriptFile())); Yii::setAlias('@web', $request->getBaseUrl()); parent::bootstrap(); }
再看看yii\base\Application的bootstrap方法
protected function bootstrap() { if ($this->extensions === null) { $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); } } 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__); } } } //已配置需要初始化的組件初始化 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__); } } }
到此new Application($config)這一步分析完畢
再來看看$app->run()做了什么
先打開yii\base\Application的run方法
public function run() { try { $this->state = self::STATE_BEFORE_REQUEST; //這里可以綁定自定義事件,類似鉤子 $this->trigger(self::EVENT_BEFORE_REQUEST); $this->state = self::STATE_HANDLING_REQUEST; //最重要的一點(diǎn) 見解釋2-1 $response = $this->handleRequest($this->getRequest()); $this->state = self::STATE_AFTER_REQUEST; $this->trigger(self::EVENT_AFTER_REQUEST); $this->state = self::STATE_SENDING_RESPONSE; //見解釋2-2 $response->send(); $this->state = self::STATE_END; return $response->exitStatus; } catch (ExitException $e) { $this->end($e->statusCode, isset($response) ? $response : null); return $e->statusCode; } }
解釋2-1:
打開yii\web\Application的handleRequest
//$request為yii\web\Request類的實(shí)例 public function handleRequest($request) { if (empty($this->catchAll)) { try { 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(); } return $this->getResponse()->redirect(Url::to($url, $e->scheme), $e->statusCode); } } else { $route = $this->catchAll[0]; $params = $this->catchAll; unset($params[0]); } try { Yii::debug("Route requested: '$route'", __METHOD__); $this->requestedRoute = $route; /* 例如訪問url為http://domain/web/index.php?r=post/index&id=3 $route為路由url字符串,得到post/index $params為Query String數(shù)組,得到['id'=>3, 'r'=> 'post/index'] $result的值為對(duì)應(yīng)conroller執(zhí)行對(duì)應(yīng)action返回的值或者對(duì)象 */ $result = $this->runAction($route, $params); if ($result instanceof Response) { return $result; } //構(gòu)造一個(gè)Response對(duì)象 $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); } }
我們進(jìn)入$this->runAction看看
public function runAction($route, $params = []) { //得到($controller實(shí)例對(duì)象和action名稱的字符串) $parts = $this->createController($route); if (is_array($parts)) { /* @var $controller Controller */ list($controller, $actionID) = $parts; $oldController = Yii::$app->controller; Yii::$app->controller = $controller; //執(zhí)行controller的對(duì)應(yīng)的actionID方法,該方法返回的內(nèi)容賦值給$result $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) . '".'); }
解釋2-2:
打開yii\web\Response的send方法
public function send() { if ($this->isSent) { return; } $this->trigger(self::EVENT_BEFORE_SEND); //取得$response對(duì)象的format再獲得該format對(duì)象的實(shí)例執(zhí)行format方法(就是header設(shè)置Content-Type) //見2-2-1 $this->prepare(); $this->trigger(self::EVENT_AFTER_PREPARE); //見2-2-2 $this->sendHeaders(); //見2-2-3 $this->sendContent(); $this->trigger(self::EVENT_AFTER_SEND); $this->isSent = true; }
解釋2-2-1:
protected function prepare() { if ($this->stream !== null) { return; } if (isset($this->formatters[$this->format])) { $formatter = $this->formatters[$this->format]; if (!is_object($formatter)) { $this->formatters[$this->format] = $formatter = Yii::createObject($formatter); } if ($formatter instanceof ResponseFormatterInterface) { $formatter->format($this); } else { throw new InvalidConfigException("The '{$this->format}' response formatter is invalid. It must implement the ResponseFormatterInterface."); } } elseif ($this->format === self::FORMAT_RAW) { if ($this->data !== null) { $this->content = $this->data; } } else { throw new InvalidConfigException("Unsupported response format: {$this->format}"); } if (is_array($this->content)) { throw new InvalidArgumentException('Response content must not be an array.'); } elseif (is_object($this->content)) { if (method_exists($this->content, '__toString')) { $this->content = $this->content->__toString(); } else { throw new InvalidArgumentException('Response content must be a string or an object implementing __toString().'); } } }
解釋2-2-2:
protected function sendHeaders() { if (headers_sent($file, $line)) { throw new HeadersAlreadySentException($file, $line); } if ($this->_headers) { $headers = $this->getHeaders(); foreach ($headers as $name => $values) { $name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $name))); // set replace for first occurrence of header but false afterwards to allow multiple $replace = true; foreach ($values as $value) { header("$name: $value", $replace); $replace = false; } } } $statusCode = $this->getStatusCode(); header("HTTP/{$this->version} {$statusCode} {$this->statusText}"); $this->sendCookies(); }
這里補(bǔ)充下sendCookies方法:
protected function sendCookies() { if ($this->_cookies === null) { return; } $request = Yii::$app->getRequest(); if ($request->enableCookieValidation) { if ($request->cookieValidationKey == '') { throw new InvalidConfigException(get_class($request) . '::cookieValidationKey must be configured with a secret key.'); } $validationKey = $request->cookieValidationKey; } foreach ($this->getCookies() as $cookie) { $value = $cookie->value; if ($cookie->expire != 1 && isset($validationKey)) { $value = Yii::$app->getSecurity()->hashData(serialize([$cookie->name, $value]), $validationKey); } setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httpOnly); } }
解釋2-2-3:
protected function sendContent() { if ($this->stream === null) { echo $this->content; return; } set_time_limit(0); // Reset time limit for big files $chunkSize = 8 * 1024 * 1024; // 8MB per chunk if (is_array($this->stream)) { list($handle, $begin, $end) = $this->stream; fseek($handle, $begin); while (!feof($handle) && ($pos = ftell($handle)) <= $end) { if ($pos + $chunkSize > $end) { $chunkSize = $end - $pos + 1; } echo fread($handle, $chunkSize); flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit. } fclose($handle); } else { while (!feof($this->stream)) { echo fread($this->stream, $chunkSize); flush(); } fclose($this->stream); } }
至此源碼整個(gè)流程分析基本完畢,有些地方可能分析不夠詳細(xì),后續(xù)再詳細(xì)補(bǔ)充。
最后附加下官網(wǎng)文檔的部分內(nèi)容幫助大家理解
以下圖表展示了一個(gè)應(yīng)用如何處理請(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ù)庫(kù)。
動(dòng)作會(huì)渲染一個(gè)視圖,把數(shù)據(jù)模型提供給它。
渲染結(jié)果返回給響應(yīng)組件。
響應(yīng)組件發(fā)送渲染結(jié)果給用戶瀏覽器。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。