您好,登錄后才能下訂單哦!
用yii2框架用了將近2年,一直都沒有去看過它底層源碼, 馬上快不用了,最近對其源碼研究一番,哈哈
廢話少說,上代碼,
入口文件是web/index.php
<?php defined('YII_DEBUG') or define('YII_DEBUG', true); defined('YII_ENV') or define('YII_ENV', 'dev'); //這行我在composer autoload流程已經分析過 require __DIR__ . '/../vendor/autoload.php'; //見解釋1-1 require __DIR__ . '/../vendor/yiisoft/yii2/Yii.php'; //配置文件 $config = require __DIR__ . '/../config/web.php'; //最關鍵的一點,見解釋1-2 (new yii\web\Application($config))->run();
解釋1-1
直接上Yii.php文件源碼
<?php require(__DIR__ . '/BaseYii.php'); class Yii extends \yii\BaseYii { } //實際上調用的是BaseYii的autoload方法,自動加載yii的類 spl_autoload_register(['Yii', 'autoload'], true, true); //yii類名和yii類名所在文件的映射數組 Yii::$classMap = require(__DIR__ . '/classes.php'); //依賴注入容器,這個后續文章再分析,先知道有這么一個東東 Yii::$container = new yii\di\Container();
解釋1-2
我們最關鍵的點來了分析application啟動流程
首先看看Application構造函數
首先進入yii\web\Application類,發現沒有構造方法,于是跟蹤它的層級關系,列出來:
yii\web\Application -> \yii\base\Application -> \yii\base\Module -> \yii\di\ServiceLocator -> \yii\base\Component
-> \yii\base\BaseObject -> \yii\base\Configurable(接口interface)
首先進入yii\base\Application找到__construct方法:
public function __construct($config = []) { //保存當前啟動的application實例 Yii::$app = $this; //將Yii::$app->loadedModules[實例類名] = 當前實例; $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:
/* 該函數作用是將配置數組進一步合并完善數組中的key $config即為入口文件包含到的config/web.php返回的數組,舉例如下: $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(); } //設置時區 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'], ]; 合并配置文件數組的components key內容 */ 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可以在文件中配,默認為true if (YII_ENABLE_ERROR_HANDLER) { if (!isset($config['components']['errorHandler']['class'])) { echo "Error: no errorHandler component is configured.\n"; exit(1); } /* 曬個默認配置 'errorHandler' => [ 'errorAction' => 'site/error', ], $this->set方法是引自\yii\di\ServiceLocator的set方法, 注冊組件$this->_definitions['erroHandler'] = ['errorAction' => 'site/error','class'=>'yii\web\ErrorHandler']; */ $this->set('errorHandler', $config['components']['errorHandler']); unset($config['components']['errorHandler']); //這個方法會實例化errorHandler的class,實例化這步實際上用到依賴注入,之前我已經講過一點,以后寫個yii2創建對象流程 //并將實例化的對象保存到$this->__components['errorHandler'] $this->getErrorHandler()->register(); } }
解釋1-2-3:
//實際調用的是yii\base\BaseObject類的構造方法 public function __construct($config = []) { if (!empty($config)) { //將$config數組中的每個key都賦值$this->本地化變量 Yii::configure($this, $config); } $this->init(); }
很明顯追蹤$this->init()方法,后面追蹤到yii\base\Application的init方法。
public function init() { $this->state = self::STATE_INIT; $this->bootstrap(); }
再看看bootstrap方法
先看看yii\web\Application的bootstrap方法
protected function bootstrap() { //獲得request對象實例 $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; //最重要的一點 見解釋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類的實例 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數組,得到['id'=>3, 'r'=> 'post/index'] $result的值為對應conroller執行對應action返回的值或者對象 */ $result = $this->runAction($route, $params); if ($result instanceof Response) { return $result; } //構造一個Response對象 $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); } }
我們進入$this->runAction看看
public function runAction($route, $params = []) { //得到($controller實例對象和action名稱的字符串) $parts = $this->createController($route); if (is_array($parts)) { /* @var $controller Controller */ list($controller, $actionID) = $parts; $oldController = Yii::$app->controller; Yii::$app->controller = $controller; //執行controller的對應的actionID方法,該方法返回的內容賦值給$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對象的format再獲得該format對象的實例執行format方法(就是header設置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(); }
這里補充下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); } }
至此源碼整個流程分析基本完畢,有些地方可能分析不夠詳細,后續再詳細補充。
最后附加下官網文檔的部分內容幫助大家理解
以下圖表展示了一個應用如何處理請求:
用戶向入口腳本 web/index.php
發起請求。
入口腳本加載應用配置并創建一個應用 實例去處理請求。
應用通過請求組件解析請求的 路由。
應用創建一個控制器實例去處理請求。
控制器創建一個動作實例并針對操作執行過濾器。
如果任何一個過濾器返回失敗,則動作取消。
如果所有過濾器都通過,動作將被執行。
動作會加載一個數據模型,或許是來自數據庫。
動作會渲染一個視圖,把數據模型提供給它。
渲染結果返回給響應組件。
響應組件發送渲染結果給用戶瀏覽器。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。