php yii框架源码,yii 源码解读
date: 2017-11-21 18:15:18
title: yii 源碼解讀
本篇博客閱讀指南:
php & 代碼提示: 工欲善其事必先利其器
yii 源碼閱讀指南: 整體上全貌上進行了解
之后的章節: 細節入手, 沒錯, 都是知識點
寫完上篇 yii 框架簡析 后, 發現干貨有點少, 寫來寫去還是 底層是服務容器 這樣的老生常談. 雖然這個真的很重要, 我認為理解服務容器的 php 程序員, 算是 境界提升(至少不用自嘲「碼畜」了吧). 這篇就實實在在的閱讀 yii 框架的源碼, 希望可以給大家帶來更多干貨.
備注: 因為 yii 有一個慣用套路, 框架層實現使用 BaseXXX, 具體使用需要用 Xxx 來繼承而不是直接使用 BaseXxx 類, 而最底層的基類 BaseObject 使用這種方式后的類 Object, 在 php7.2 中被添加為關鍵字.
請使用 phpstorm
詳細介紹一個 IDE 怎么用不太現實, 各種黑科技還是自己體會, 我比較喜歡憑自己落筆時的印象來判斷 -- 第一時間想到的, 往往是最熟悉的.
錯誤提示: 單詞拼寫錯誤, 低級語法錯誤, 這些開發過程中最常見的問題
代碼提示: 函數以及函數的參數和返回; 類以及類的屬性和方法; 等等等等
跳轉: 方便的跳轉對閱讀代碼有多重要就不多說了, 而且可以跳轉 php 內部函數和類, 明顯減少 php manual 的使用
還有其他很多高級功能, 比如 重構/db連接/版本控制, 這些都不是重點, 或者說錦上添花, 用 phpstorm 的理由非常簡單:
明顯提高開發效率. 崇尚極簡也同樣適用, 只關心編輯功能也會發現效率提升.
友情提示: 開發機請使用 16G 內存. 更多使用小技巧可以參考我的 wiki - tools - ide
代碼提示
phpstorm 之所以會讓人感覺很 智能, 很多地方都來自于完善的 代碼提示. 當然現實是很多人寫代碼, 不寫注釋.
我 TM 代碼都寫不完, 你還要我寫注釋?!
不就這個話題展開, 但是可以給一個關于開源代碼選用的標準, 如果你打算使用的開源代碼注釋和文檔不完善, 建議你最好不要選用. 否則, 一定要確認可以對接的人(同樣適用于接手維護舊代碼).
這里八卦一下, 之前一直有人 罵 swoole 文檔爛, 到處是 坑. 我這里說句公道話, swoole 的 wiki 里寫到有 2 個開源項目提供代碼提示(關于代碼提示, 可以參考之前的 blog - 聊一聊 php 代碼提示), 一種使用 php Reflection(反射) 實現, 一種是提取代碼注釋然后手動完善. 并且 swoole 的 wiki 1400+ 頁, 下面的評論往往也是干貨滿滿. 在你罵別人文檔爛, 坑多的時候, 你憑什么這樣說?
能力足夠, 可以參加核心開發組; 文檔不夠完善, 但是它可直接編輯; 使用發現代碼提示不夠好, 代碼提示的開源項目可以參與. 最后是坑多的說法, 有沒有想過更多是經驗不夠, 而不是工具不好用.
發這個牢騷不是想 探究人性 之類的, 只是面對有些現實, 其實明明可以往 好一點 的方向前進一小步. 當然, 我可不敢公然和 噴子 叫板.
得益于 php 語言的簡單, 代碼提示在這里也非常簡單, 而且 yii 框架的代碼提示做得非常好, 幾乎任何輸入的地方, 都會有 IDE 的自動提示.
注釋的語法很簡單: 指令(@開頭) + 指令內容. 基本都是只要看到就能理解什么意思:
// 描述函數參數, 格式: @param type var define
@param string $name the property name
// 描述函數返回值, 格式和上面類似: @return type define
@return mixed the property value or the value of a behavior's property
// 變量提示, 格式也類似: @var type define
@var array the attached event handlers (event name => handlers)
當然還有一些其他提示, 各有作用, 使用較少, 就不一一列舉了.
備注: 使用 swoole 的過程中也被回調函數難以代碼提示煩惱過, 所以參與了 swoole-ide-helper 項目, 提交 pr, 來一起改善 swoole 的編程體驗.
道理很多依舊過不好人生什么的, 原因可能并沒有那么復雜, 真的只是因為太懶了一點.
yii 源碼閱讀指南
這里有一份 yii 源碼閱讀過程中制作的 百度腦圖 - yii 源碼解析, 方便查看類的依賴關系, yii 源碼的層次結構. 有種說法是一圖勝千言, 希望能起到這樣的效果.
yii 框架的源碼很簡單, 層次很清晰:
yii\base\BaseObject: 基類, 幾乎所有類都繼承自這個類, 使用 __get()/__set() 等魔術方法, 方便操作類屬性等
class BaseObject implements Configurable
{
public static function className()
{
return get_called_class(); // 等同于 static::CLASS, 區別與 get_class()
}
public function __get($name){};
public function __set($name, $value)
}
這里實現了 Configurable 接口, 給框架了帶來了 基于配置 的超強靈活性, 后面會有具體代碼講到
yii\base\Component: 組件, 繼承自 BaseObject, yii 框架提供的所有功能, 幾乎都是 component, 這樣就可以 \Yii:$db 這樣的形式來調用
class Component extends BaseObject
{
private $_events = [];
private $_behaviors;
}
Component 擴展了 BaseObject, 并為所有組件定義了特性: property, event and behavior
\yii\di\Container: 容器, 這個概念就不再啰嗦
\BaseYii: yii 框架主體, 還定義了部分框架運行時的輔助功能, log, profile 等
\Yii: 實例化 BaseYii, 這種方式 yii 中隨處可見, Base 定義基礎功能, 具體使用時繼承基類并自己按需擴展. \Yii 會同時啟動一個 Container.
class Yii extends \yii\BaseYii
{
}
spl_autoload_register(['Yii', 'autoload'], true, true);
Yii::$classMap = require __DIR__ . '/classes.php';
Yii::$container = new yii\di\Container();
這里使用 classMap 的方式來注冊框架核心類, 性能會比 composer 的 psr-4 稍高, 但是也導致了你有 2 種方式來管理依賴, 這點我是持 消極 態度的.
web\index.php: 入口腳本, 加載配置和 Yii, 實例化 application, 來完成請求
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');
require(__DIR__ . '/../vendor/autoload.php');
require(__DIR__ . '/../../vendor/yiisoft/yii2/Yii.php');
$config = require(__DIR__ . '/../config/web.php');
(new yii\web\Application($config))->run();
得益于 BaseObject 和 Component, 幾乎所有特性, 都可以通過這里的 $config 進行配置.
之后的功能, 就是一個一個 Component 了, 這里不贅述了, 通過 百度腦圖 - yii 源碼解析 非常方便的查看. 以前 model module 傻傻分不清楚的情況, 是不是迎刃而解了?
關于鏈式調用
這次閱讀源碼的過程中, 在使用 yii\widgets\DetailView 卡了一小會, 被自己之前關于鏈式調用的理解給繞進去了. 首先看第一種方式 $this:
class a {
public $b = 0;
function aa() {
$this->b += 1;
return $this;
}
function bb() {
$this->b += 2;
return $this;
}
}
$a = new a();
$a->aa()->bb()->aa();
echo $a->b;
通過在類方法中返回 $this, 從而實現鏈式調用, 這樣的寫法, 可以參考 yii\db\Query 的源碼, 使用鏈式調用來構建 sql 語句.
因為對 $this 這種方式 印象太深, 導致忽略了下面這種更常見的方式:
class A {
public function b()
{
$b = new b();
return $b; // 返回 b 對象
}
}
class B {
public function c() {
echo 'czl';
}
}
$a = new A();
$a->b()->c();
使用 其他對象作為自己的屬性或者函數返回值, 這是更常見的鏈式調用, 而在 yii 中, 這種方式更是隨處可見, 這里用 \yii\widgets\DetailView 中使用 \yii\i18n\Formatter 來展示一下 基于配置 的超強靈活性:
DetailView::widget([
'model' => $model, // 和 Model 類無縫配合
'attributes' => [
'id',
'title',
'content:ntext',
'tags:ntext',
'create_time:datetime',
'update_time:datetime',
[
'attribute' => 'author_id',
'value' => $model->author->nickname,
],
],
'template' => '
{label}{value}','formatter' => [
'class' => \yii\i18n\Formatter::class,
'datetimeFormat' => 'short',
]
]);
查看 api 文檔, 會發現這里的 attribute 非常的強大:
這里的 attribute 屬性, 可以和 Model 中的 attribute 屬性一一對應
這里的 attribute 屬性, 可以使用 attribute:format:label 格式, 其中的 format 就是對應的
\yii\i18n\Formatter, 大部分常用的格式化方法, 這里都有定義, 比如這里的 create_time:datetime 表示使用 \yii\i18n\Formatter 中的 asDatetime() 進行格式化
你以為到這里就結束了么:
template: 直接可以配置頁面的 html
formatter: 不止可以用 \yii\i18n\Formatter, 還可以配置
還沒完, 我們在全局也是可以配置的 config/web.php:
$config = [
'id' => 'myYii',
...
'components' => [
'formatter' => [
'datetimeFormat' => 'Y-m-d H:i:s',
]
],
];
當然, 全局的配置, 會被這里具體使用的地方給覆蓋掉.
另外還有 \yii\widgets\ActiveForm 和 \yii\widgets\ActiveField 的源碼, 也是這樣的方式提供超強的靈活性
非常推薦大家閱讀一下這塊的代碼, 嘗試動手改改, 只要這里理解清楚了, 對框架的整體理解基本沒問題了.
PS: 我之前表達過觀點, 前后端分離是大勢, phper 應該更關注 后端, 關注寫出更好的 api. 但是 yii 這種前后端無縫對接高可配置的方式, 還是把我驚艷到了. 但是我的觀點還是沒有變, phper 還是應該更關注后端, 我傾向于把 yii 應用到不需要 設計 的場合, 比如管理后臺.
關于 db
很多初級 phper 會感覺 db 這塊的內容 很多, 一方面是數據庫相關的基礎知識就很多(基礎的增刪改查并不是難度好不好), 然后 php 和數據庫聯動的過程, 又增加了一層抽象. 我之前的 blog - hyperframework WebClient 源碼解讀 也提過這樣一個觀點:
層出不窮的工具, 目的就是對現有問題作出更 易用 的抽象. 但是伴隨抽象的不斷增多, 基礎部分的更加不可見, 導致越來越容易 摸不著頭腦. 所以我希望我寫的東西, 能在一開始就給大家劃定出一個核心的范圍, 而不是又一個工具的堆砌.
先來聊 db 的第一個話題, php 使用 db 的三種方式.
3 種 db 訪問方式
數據庫作為一個服務, 其實 php 是作為 client 端來訪問. 數據庫的架構通常是分層結構, 最外層的和我們平時寫的 接口 網關 其實是一樣的 -- 通過暴露 api 來提供服務. 只是我們最終提供的 web 服務, 走的是 http 協議, 而數據庫走的數據庫的協議, 比如和 mysql 通信需要實現 mysql 協議. 嗯, 這個比較底層了, 協議的細節被抽象掉了, 最終暴露給我們的, 其實就是 sql.
這就是我劃定的核心范圍, 說是 3 種方式, 本質還是執行 sql 語句而已.
// 直接執行 sql 語句
$postStatus = \Yii::$app->db->createCommand('SELECT id,`name` FROM poststatus')->queryAll();
$postStatus = array_column($postStatus, 'name', 'id');
// 使用查詢構造器
$postStatus = (new \yii\db\Query())
->select(['name', 'id'])
->from('poststatus')
->indexBy('id')
->column();
// 使用 ActiveRecord
$postStatus = \app\models\Poststatus::find()
->select(['name', 'id'])
->indexBy('id')
->column();
三種方式的關系也很簡單:
ActiveRecord 調用 find() 后, @return ActiveQuery the newly created [[ActiveQuery]] instance, 其實就是返回一個拼上表名的 ActiveQuery 實例
ActiveQuery 通過鏈式調用, 拼接出一個完整的 sql
最終和 \Yii::$app->db->createCommand() 執行沒啥區別, 只是 ActiveQuery 又提供了一些方法, 對查詢到的結果集做一些處理
這也是目前大部分框架采用的方式 -- 提供三種方式給大家使用. 這里還是發表一下我個人的觀點, 我們的 hyperframework 中是不提供 ActiveQuery 這樣的實現的, 因為我們相信, 大部分情況下, sql 是更好的選擇:
實現一個 ActiveQuery 類并不難, 用起來也不難, 但是 sql 是必須要掌握的, 掌握了 sql 之后其實就可以用第一種方法解決問題了
ActiveQuery 在復雜 sql 下面非常難寫, 甚至不能 -- 來自游戲數據統計的血淚史
當然, ActiveQuery 也有優點和合適的場景, 比如代碼提示和條件查詢:
$query = $db->select('xxx');
if (!empty($search['a'])) {
$query = $query->where('a', $search['a']);
}
關聯查詢
上一節只是 淺嘗輒止 的提到 ActiveRecord, 這里詳細講講, 然后再深入一點. 先提個醒: 設計出 ActiveRecord 這樣的抽象, 真的非常厲害.
ActiveRecord, 中文翻譯為活動記錄, 對應于 MVC 中的 Model 這一層, 但是它是和數據庫結合最緊密的地方. 一個 ActiveRecord 類, 用來對應數據庫里的一張表, 一個 ActiveRecord 實例化對象, 用來對應這張表里面的一條記錄, 進而通過對象的 新建/屬性修改/方法調用, 實現數據庫的增刪改查.
// 增
$post = new Post();
$post->title = 'daydaygo';
$post->save();
// 查
$post = Post::find(1);
// 刪
$post->delete();
// 改
$post->title = 'czl';
$post->save();
你看這樣的代碼, 是不是感受不到 sql 的存在, 但是你卻輕松實現了需要的功能. 這就是我認為 厲害的地方.
再來看更厲害的 -- 關聯查詢:
// Post 中定義和 author 的關聯
public function getAuthor()
{
return $this->hasOne(Adminuser::className(), ['id' => 'author_id']);
}
// 這樣訪問 author 就簡單了
$post->author;
這里先解釋一下, $post->author 會去尋找 Post 中的 getAuthor() 方法, 然后根據這里定義的關聯關系, 執行查詢, 并將查詢到 author 記錄, 賦值給 $post->author 屬性. 這里有 2 個細節:
author -> getAuthor() 其實是通過 yii\db\BaseActiveRecord 中的 __get() 魔術方法實現的, 這也是 yii 核心的設計理念之一, 通過實現 __get() 等魔術方法, 讓 類 更好用
Post 的注釋中有這樣一句 @property Adminuser $author, 這樣使用 $post->author 就有酸爽的代碼提示了
關于關聯查詢, 這里還有 2 個細節:
查詢緩存, 這也是 yii 為什么性能這么高的原因. 一點題外話, 在看源碼的過程中, 有函數被標記不推薦使用, 點進入發現是使用緩存的姿勢不夠優雅, 強耦合
// 關聯查詢
$user = User::findOne();
$orders = $user->orders; // 執行關聯查詢, 結果被緩存
unset($user->orders); // 清楚緩存, 重新查詢
$orders2 = $user->orders;
多對多的查詢, 需要注意查詢上的優化:
// 多次查詢
$users = User::find()->all(); // 查詢 user
foreach($users as $user){
$oders = $user->orders; // 查詢 order
}
$users = User::find()->with('orders')->all(); // 2次查詢, 一次 user, 一次 order
foreach($users as $user){
$oders = $user->orders; // 此處不會執行數據庫查詢
}
關于鎖
基礎稍差的話, 可能對鎖的概念會有些陌生. 簡單的解釋是: 在多進程或者多線程編程的情況下, 同時訪問同一個資源導致程序的最終結果不可控.
首選需要區分 2 個概念: 并發 vs 并行
并發 Concurrent: 多線程多進程場景下, 微觀上 cpu 進行調度切換, 快到人類無法直觀感知極限(0.1s), 所以宏觀上看起來是 同時 運行
并行 parallel: 真正的 同時 運行, 必須要都多 cpu 支持
再來一個概念: 競態資源
在某個資源上產生了并發訪問, 導致程序執行后沒有達到預期, 那么這個資源就是競態資源
套用一下數據事務的例子: 2 個賬戶間轉賬, 必須加事務, 只有一個賬戶上錢扣了, 另一個賬戶上錢增加了, 才算完成, 這時候去取到的 2 個賬戶的余額才是準確的.
好了, 前戲差不多了, 這里來講講 yii 中用到的 2 個鎖.
mutex 互斥鎖
yii 中特地添加了 yii\mutex\Mutex, 并且提供了不同驅動下(file, 不同 db)的實現
互斥鎖的理念非常簡單: 保證當前只有一個進程(或線程)訪問當前資源
實現也非常簡單, 就 2 個方法:
acquire(): 使用前請求鎖, 請求成功就繼續執行業務邏輯, 失敗就退出
release(): 使用后釋放鎖
function lock($lockName = NULL) {
if (empty($lockName)) {
$backtrace = debug_backtrace(null, 2);
$class = $backtrace[1]['class'];
$func = $backtrace[1]['function'];
$args = implode('_', $backtrace[1]['args']);
$lockName = base64_encode($class . $func . $args);
}
$lock = \Yii::$app->mutex->acquire( $lockName ); // 請求鎖
if (!$lock) {
$err = "cannot get lock {$lockName}.";
throw new \Exception($err);
}
register_shutdown_function(function() use($lockName) {
return \Yii::$app->mutex->release($lockName); // 釋放鎖
});
return TRUE;
}
db optimisticLock() 樂觀鎖
這個就隱藏的比較深了. 因為已經養成數據庫中使用自動更新的 create_time / update_time 字段, 所以深入 ActiveRecord 的 update() 源碼進去, 然后就發現了這家伙. 詳細的解釋可以看這里 百度百科 - 樂觀鎖
/**
* @see update()
* @param array $attributes attributes to update
* @return int|false the number of rows affected, or false if [[beforeSave()]] stops the updating process.
* @throws StaleObjectException
*/
protected function updateInternal($attributes = null)
{
if (!$this->beforeSave(false)) {
return false;
}
$values = $this->getDirtyAttributes($attributes);
if (empty($values)) {
$this->afterSave(false, $values);
return 0;
}
$condition = $this->getOldPrimaryKey(true);
$lock = $this->optimisticLock(); // 開始使用樂觀鎖
if ($lock !== null) {
$values[$lock] = $this->$lock + 1;
$condition[$lock] = $this->$lock;
}
// We do not check the return value of updateAll() because it's possible
// that the UPDATE statement doesn't change anything and thus returns 0.
$rows = static::updateAll($values, $condition);
if ($lock !== null && !$rows) {
throw new StaleObjectException('The object being updated is outdated.');
}
if (isset($values[$lock])) {
$this->$lock = $values[$lock];
}
$changedAttributes = [];
foreach ($values as $name => $value) {
$changedAttributes[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
$this->_oldAttributes[$name] = $value;
}
$this->afterSave(false, $changedAttributes);
return $rows;
}
關于 log & error handler
寫代碼到一定程度, 就會開始意識到 log & error handler 的重要性, 然而在小白程序員升級打怪的過程中, 一直在寫業務, 這 2 塊關注太少以致有些 蒼白. 并且這塊也是我比較薄弱的地方, 幾個月前在添加 Exception 的時候卡住了.
知道短處, 補補就好了.
在聊這 2 塊之前, 先補一下關于 回調 的基礎知識:
平時 phper 可能這樣寫代碼的情況不多, 不過如果接觸過 swoole, 寫過一段時間的 異步編程, 這個知識點就再熟悉不過了, 在 swoole 的 wiki 中也特意提到過, 里面列舉了 4 種, 官方文檔這里列舉了 5 種.
log 模塊
先看整體結構:
├── Logger.php
├── Dispatcher.php
└── Target.php
├── DbTarget.php
├── EmailTarget.php
├── FileTarget.php
└── SyslogTarget.php
由 Logger - Dispatcher - Target 的 3 層結構:
Logger: 日志 入口(生產者)
Dispatcher: 日志的 分發(通道)
Target: 日志 處理(消費者)
其實日志系統的設計已經相當成熟了, 幾乎都采用 消息隊列 的設計模式:
生產者 - 消費者 模型.
這里看一點代碼細節:
yii 框架中的 profile 功能, 可能大家有用過, 也是通過 Logger 實現的
// 使用
\Yii::beginProfile('block1');
// some code to be profiled
\Yii::beginProfile('block2');
// some other code to be profiled
\Yii::endProfile('block2');
\Yii::endProfile('block1');
// 實現
public static function beginProfile($token, $category = 'application')
{
static::getLogger()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category);
}
// yii\log\Logger
const LEVEL_PROFILE_BEGIN = 0x50
const LEVEL_PROFILE_END = 0x60
使用 flush: 先 緩存 一下, 然后再一起落地, 性能要比直接寫直接落地高一些
public function log($message, $level, $category = 'application')
{
$time = microtime(true);
$traces = [];
// ...
$this->messages[] = [$message, $level, $category, $time, $traces, memory_get_usage()]; // 暫時緩存到這里
if ($this->flushInterval > 0 && count($this->messages) >= $this->flushInterval) {
$this->flush();
}
}
回調終于登場了, register_shutdown_function() 函數下面還會看到
public function init()
{
parent::init();
register_shutdown_function(function () {
// make regular flush before other shutdown functions, which allows session data collection and so on
$this->flush();
// make sure log entries written by shutdown functions are also flushed
// ensure "flush()" is called last when there are multiple shutdown functions
register_shutdown_function([$this, 'flush'], true);
});
}
日志模塊的代碼還是很簡單的. 實現日志模塊其實并不難, 但是新手想用好日志卻感覺有點 經驗積累 的意思, 特別是遇到的問題的時候發現沒有日志輔助定位問題. 我的建議也很簡單:
多打日志, 多用日志.
error handler 模塊
如果說日志大部分時候只用 Logger::info() 這樣調用一下就好了, Exception 天生就要復雜一點了, 因為完整的過程是這樣的:
try {
// do something
throw new \Exception("Error Processing Request", 1);
} catch (\Exception $e) {
// handle error
}
但是, 其實只要記住這個基本 骨架, 任何地方都是同樣的. 如果這塊比較薄弱, 可以 參考官方手冊 - Exception 多看一看.
yii 框架中 Exception 的使用很多, 所以看起來會比較凌亂, 但其實層次很清晰:
首先是 base, 這基本確定了 Exception 的分類:
\yii\base\Exception: 異常基類, 統一添加 getName() 方法給異常添加標識
\yii\base\ErrorException: 處理未捕獲的 php 錯誤和異常, 下面會著重講一下 register() 方法
\yii\base\UserException: 用戶可見異常基類, 這個很重要, 添加了一個明顯分類
\yii\base\XxxException: 其他異常
然后就是根據應用不同:
\yii\web\XxxException: web 應用下的異常
\yii\console\XxxException: console 應用下的異常
好了, 再來看點源碼:
\yii\base\ErrorException 中的 register() 方法: 注冊函數回調; 兼容 HHVM
public function register()
{
ini_set('display_errors', false);
set_exception_handler([$this, 'handleException']);
if (defined('HHVM_VERSION')) {
set_error_handler([$this, 'handleHhvmError']);
} else {
set_error_handler([$this, 'handleError']);
}
if ($this->memoryReserveSize > 0) {
$this->_memoryReserve = str_repeat('x', $this->memoryReserveSize);
}
register_shutdown_function([$this, 'handleFatalError']);
}
\yii\base\UserException
/**
* UserException is the base class for exceptions that are meant to be shown to end users.
* Such exceptions are often caused by mistakes of end users.
*/
class UserException extends Exception
{
}
一個明顯的場景, 就是 http 的 4xx 錯誤:
class HttpException extends UserException
{
/**
* @var int HTTP status code, such as 403, 404, 500, etc.
*/
public $statusCode;
}
還有一個常用的方式(套路), 將應用整個包在 try-catch 中, 統一捕獲異常
// 入口腳本: web/index.php
require(__DIR__ . '/../../vendor/yiisoft/yii2/Yii.php');
$config = require(__DIR__ . '/../config/web.php');
(new yii\web\Application($config))->run();
// \yii\base\Application
public function run()
{
try {
$this->state = self::STATE_BEFORE_REQUEST;
$this->trigger(self::EVENT_BEFORE_REQUEST);
$this->state = self::STATE_HANDLING_REQUEST;
$response = $this->handleRequest($this->getRequest());
$this->state = self::STATE_AFTER_REQUEST;
$this->trigger(self::EVENT_AFTER_REQUEST);
$this->state = self::STATE_SENDING_RESPONSE;
$response->send();
$this->state = self::STATE_END;
return $response->exitStatus;
} catch (ExitException $e) {
$this->end($e->statusCode, isset($response) ? $response : null);
return $e->statusCode;
}
}
寫在最后
聊了這么多, 內容多了之后, 也會有些 雜亂, 而且也無法深入到太多的細節. 我比較滿意的是, 在一開始我就計劃好使用腦圖, 嘗試整體的理解架構, 那些記下的細節, 反而有點像 意外之喜.
大型開源項目的源碼是一座寶礦. 編程也是一項技藝, 如同江湖中對武功的崇拜一樣, 程序員也會對自己的一技之長產生驕傲.
也許確實沒有大段的時間去閱讀源碼, 但是使用方法時, 多點進去看看, 也經常會有所收獲, 比如 yii 中 cache 相關的方法:
// 平時使用
Yii::$app->cache->set('key', 'value');
// 進入會發現, 可以設置 過期時間 + 緩存依賴
public function set($key, $value, $duration = null, $dependency = null)
總結
以上是生活随笔為你收集整理的php yii框架源码,yii 源码解读的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 预计这个价!曝vivo V27系列将于3
- 下一篇: python 免费空间_总算找到php免