從0開始構建一個屬於你自己的PHP框架
如何構建一個自己的PHP框架
為什麼我們要去構建一個自己的PHP框架?可能絕大多數的人都會說“市面上已經那麼多的框架了,還造什麼輪子?”。我的觀點“造輪子不是目的,造輪子的過程中汲取到知識才是目的”。
那怎樣才能構建一個自己的PHP框架呢?大致流程如下:
- 入口檔案 —-> 註冊自載入函式
- —-> 註冊錯誤(和異常)處理函式
- —-> 載入配置檔案
- —-> 請求
- —-> 路由
- —->(控制器 <—-> 資料模型)
- —-> 響應
- —-> json
- —-> 檢視渲染資料
除此之外我們還需要單元測試、nosql支援、介面文件支援、一些輔助指令碼等。最終我的框架目錄如下:
框架目錄一覽
- app [PHP應用目錄]
- ├── demo [模組目錄]
- │ ├── controllers [控制器目錄]
- │ │ └── Index.php [預設控制器檔案,輸出json資料]
- │ ├── logics [邏輯層,主要寫業務邏輯的地方]
- │ │ ├── exceptions [異常目錄]
- │ │ ├── gateway [一個邏輯層實現的gateway演示]
- │ │ ├── tools [工具類目錄]
- │ │ └── UserDefinedCase.php [註冊框架載入到路由前的處理用例]
- │ └── models [資料模型目錄]
- │ └── TestTable.php [演示模型檔案,定義一一對應的資料模型]
- ├── config [配置目錄]
- │ ├── demo [模組配置目錄]
- │ │ ├── config.php [模組自定義配置]
- │ │ └── route.php [模組自定義路由]
- │ ├── common.php [公共配置]
- │ ├── database.php [資料庫配置]
- │ └── nosql.php [nosql配置]
- docs [介面文件目錄]
- ├── apib [Api Blueprint]
- │ └── demo.apib [介面文件示例檔案]
- ├── swagger [swagger]
- framework [Easy PHP核心框架目錄]
- ├── exceptions [異常目錄]
- │ ├── CoreHttpException.php[核心http異常]
- ├── handles [框架執行時掛載處理機制類目錄]
- │ ├── Handle.php [處理機制介面]
- │ ├── ErrorHandle.php [錯誤處理機制類]
- │ ├── ExceptionHandle.php [未捕獲異常處理機制類]
- │ ├── ConfigHandle.php [配置檔案處理機制類]
- │ ├── NosqlHandle.php [nosql處理機制類]
- │ ├── LogHandle.php [log機制類]
- │ ├── UserDefinedHandle.php[使用者自定義處理機制類]
- │ └── RouterHandle.php [路由處理機制類]
- ├── orm [物件關係模型]
- │ ├── Interpreter.php [sql解析器]
- │ ├── DB.php [資料庫操作類]
- │ ├── Model.php [資料模型基類]
- │ └── db [資料庫類目錄]
- │ └── Mysql.php [mysql實體類]
- ├── nosql [nosql類目錄]
- │ ├── Memcahed.php [Memcahed類檔案]
- │ ├── MongoDB.php [MongoDB類檔案]
- │ └── Redis.php [Redis類檔案]
- ├── App.php [框架類]
- ├── Container.php [服務容器]
- ├── Helper.php [框架助手類]
- ├── Load.php [自載入類]
- ├── Request.php [請求類]
- ├── Response.php [響應類]
- ├── run.php [框架應用啟用指令碼]
- frontend [前端原始碼和資源目錄]
- ├── src [資源目錄]
- │ ├── components [vue元件目錄]
- │ ├── views [vue檢視目錄]
- │ ├── images [圖片]
- │ ├── …
- ├── app.js [根js]
- ├── app.vue [根元件]
- ├── index.template.html [前端入口檔案模板]
- ├── store.js [vuex store檔案]
- public [公共資源目錄,暴露到全球資訊網]
- ├── dist [前端build之後的資源目錄,build生成的目錄,不是釋出分支忽略該目錄]
- │ └── …
- ├── index.html [前端入口檔案,build生成的檔案,不是釋出分支忽略該檔案]
- ├── index.php [後端入口檔案]
- runtime [臨時目錄]
- ├── logs [日誌目錄]
- ├── build [php打包生成phar檔案目錄]
- tests [單元測試目錄]
- ├── demo [模組名稱]
- │ └── DemoTest.php [測試演示]
- ├── TestCase.php [測試用例]
- vendor [composer目錄]
- .git-hooks [git鉤子目錄]
- ├── pre-commit [git pre-commit預commit鉤子示例檔案]
- ├── commit-msg [git commit-msg示例檔案]
- .babelrc [babel配置檔案]
- .env [環境變數檔案]
- .gitignore [git忽略檔案配置]
- build [php打包指令碼]
- cli [框架cli模式執行指令碼]
- LICENSE [lincese檔案]
- logo.png [框架logo圖片]
- composer.json [composer配置檔案]
- composer.lock [composer lock檔案]
- package.json [前端依賴配置檔案]
- phpunit.xml [phpunit配置檔案]
- README-CN.md [中文版readme檔案]
- README.md [readme檔案]
- webpack.config.js [webpack配置檔案]
- yarn.lock [yarn lock檔案]
框架模組說明:
入口檔案
定義一個統一的入口檔案,對外提供統一的訪問檔案。對外隱藏了內部的複雜性,類似企業服務匯流排的思想。
- // 載入框架執行檔案
- require(`../framework/run.php`);
自載入模組
使用spl_autoload_register函式註冊自載入函式到__autoload佇列中,配合使用名稱空間,當使用一個類的時候可以自動載入(require)類檔案。註冊完成自載入邏輯後,我們就可以使用use和配合名稱空間申明對某個類檔案的依賴。
錯誤和異常模組
指令碼執行期間:
- 錯誤:
通過函式set_error_handler註冊使用者自定義錯誤處理方法,但是set_error_handler不能處理以下級別錯誤,E_ERROR、
E_PARSE、 E_CORE_ERROR、 E_CORE_WARNING、 E_COMPILE_ERROR、
E_COMPILE_WARNING,和在 呼叫 set_error_handler() 函式所在檔案中產生的大多數
E_STRICT。所以我們需要使用register_shutdown_function配合error_get_last獲取指令碼終止執行的最後錯誤,目的是對於不同錯誤級別和致命錯誤進行自定義處理,例如返回友好的提示的錯誤資訊。
[ file: framework/hanles/ErrorHandle.php ]
異常:
通過函式set_exception_handler註冊未捕獲異常處理方法,目的捕獲未捕獲的異常,例如返回友好的提示和異常資訊。
[ file: framework/hanles/ExceptionHandle.php ]
配置檔案模組
載入框架自定義和使用者自定義的配置檔案。
[ file: framework/hanles/ConfigHandle.php ]
輸入和輸出
- 定義請求物件:包含所有的請求資訊
- 定義響應物件:申明響應相關資訊
框架中所有的異常輸出和控制器輸出都是json格式,因為我認為在前後端完全分離的今天,這是很友善的,目前我們不需要再去考慮別的東西。
[ file: framework/Request.php ]
[ file: framework/Response.php ]
路由模組
通過使用者訪問的url資訊,通過路由規則執行目標控制器類的的成員方法。我在這裡把路由大致分成了四類:
傳統路由
- domain/index.php?module=Demo&contoller=Index&action=test&username=test
pathinfo路由
- domain/demo/index/modelExample
使用者自定義路由
- // 定義在config/moduleName/route.php檔案中,這個的this指向RouterHandle例項
- $this->get(`v1/user/info`, function (FrameworkApp $app) {
- return `Hello Get Router`;
- });
微單體路由
我在這裡詳細說下這裡所謂的微單體路由,面向SOA和微服務架構大行其道的今天,有很多的團隊都在向服務化邁進,但是服務化過程中很多問題的複雜度都是指數級的增長,例如分散式的事務,服務部署,跨服務問題追蹤等等。這導致對於小的團隊從單體架構走向服務架構難免困難重重,所以有人提出來了微單體架構,按照我的理解就是在一個單體架構的SOA過程,我們把微服務中的的各個服務還是以模組的方式放在同一個單體中,比如:
- app
- ├── UserService [使用者服務模組]
- ├── ContentService [內容服務模組]
- ├── OrderService [訂單服務模組]
- ├── CartService [購物車服務模組]
- ├── PayService [支付服務模組]
- ├── GoodsService [商品服務模組]
- └── CustomService [客服服務模組]
如上,我們簡單的在一個單體裡構建了各個服務模組,但是這些模組怎麼通訊呢?如下:
- App::$app->get(`demo/index/hello`, [
- `user` => `TIGERB`
- ]);
通過上面的方式我們就可以鬆耦合的方式進行單體下各個模組的通訊和依賴了。與此同時,業務的發展是難以預估的,未來當我們向SOA的架構遷移時,很簡單,我們只需要把以往的模組獨立成各個專案,然後把App例項get方法的實現轉變為RPC或者REST的策略即可,我們可以通過配置檔案去調整對應的策略或者把自己的,第三方的實現註冊進去即可。
[ file: framework/hanles/RouterHandle.php ]
傳統的MVC模式提倡為MCL模式
傳統的MVC模式包含model-view-controller層,絕大多時候我們會把業務邏輯寫到controller層或model層,但是慢慢的我們會發現程式碼難以閱讀、維護、擴充套件,所以我在這裡強制增加了一個logics層。至於,邏輯層裡怎麼寫程式碼怎麼,完全由你自己定義,你可以在裡面實現一個工具類,你也可以在裡面再新建子資料夾並在裡面構建你的業務邏輯程式碼,你甚至可以實現一個基於責任連模式的閘道器(我會提供具體的示例)。這樣看來,我們的最終結構是這樣的:
- M: models, 職責只涉及資料模型相關操作
- C: controllers, 職責對外暴露資源,前後端分離架構下controllers其實就相當於json格式的檢視
- L: logics, 職責靈活實現所有業務邏輯的地方
logics邏輯層
邏輯層實現閘道器示例:
我們在logics層目錄下增加了一個gateway目錄,然後我們就可以靈活的在這個目錄下編寫邏輯了。gateway的結構如下:
- gateway [Logics層目錄下gateway邏輯目錄]
- ├── Check.php [介面]
- ├── CheckAppkey.php [檢驗app key]
- ├── CheckArguments.php [校驗必傳引數]
- ├── CheckAuthority.php [校驗訪問許可權]
- ├── CheckFrequent.php [校驗訪問頻率]
- ├── CheckRouter.php [閘道器路由]
- ├── CheckSign.php [校驗簽名]
- └── Entrance.php [閘道器入口檔案]
閘道器入口類主要負責閘道器的初始化,程式碼如下:
- // 初始化一個:必傳引數校驗的check
- $checkArguments = new CheckArguments();
- // 初始化一個:app key check
- $checkAppkey = new CheckAppkey();
- // 初始化一個:訪問頻次校驗的check
- $checkFrequent = new CheckFrequent();
- // 初始化一個:簽名校驗的check
- $checkSign = new CheckSign();
- // 初始化一個:訪問許可權校驗的check
- $checkAuthority = new CheckAuthority();
- // 初始化一個:閘道器路由規則
- $checkRouter = new CheckRouter();
- // 構成物件鏈
- $checkArguments->setNext($checkAppkey)
- ->setNext($checkFrequent)
- ->setNext($checkSign)
- ->setNext($checkAuthority)
- ->setNext($checkRouter);
- // 啟動閘道器
- $checkArguments->start(
- APP::$container->getSingle(`request`)
- );
實現完成這個gateway之後,我們如何在框架中去使用呢?在logic層目錄中我提供了一個user-defined的實體類,我們把gateway的入口類註冊到UserDefinedCase這個類中,示例如下:
- /**
- * 註冊使用者自定義執行的類
- *
- * @var array
- */
- private $map = [
- // 演示 載入自定義閘道器
- `AppDemoLogicsGatewayEntrance`
- ];
這樣這個gateway就可以工作了。接著說說這個UserDefinedCase類,UserDefinedCase會在框架載入到路由機制之前被執行,這樣我們就可以靈活的實現一些自定義的處理了。這個gateway只是個演示,你完全可以天馬行空的組織你的邏輯~
檢視View去哪了?由於選擇了完全的前後端分離和SPA(單頁應用), 所以傳統的檢視層也因此去掉了,詳細的介紹看下面。
使用Vue作為檢視
原始碼目錄
完全的前後端分離,資料雙向繫結,模組化等等的大勢所趨。這裡我把我自己開源的vue前端專案結構 easy-vue 移植到了這個專案裡,作為檢視層。我們把前端的原始碼檔案都放在frontend目錄裡,詳細如下,你也可以自己定義:
- frontend [前端原始碼和資源目錄,這裡存放我們整個前端的原始碼檔案]
- ├── src [資源目錄]
- │ ├── components [編寫我們的前端元件]
- │ ├── views [組裝我們的檢視]
- │ ├── images [圖片]
- │ ├── …
- ├── app.js [根js]
- ├── app.vue [根元件]
- ├── index.template.html [前端入口檔案模板]
- └── store.js [狀態管理,這裡只是個演示,你可以很靈活的編寫檔案和目錄]
build步驟
- yarn install
- DOMAIN=http://你的域名 npm run dev
編譯後
build成功之後會生成dist目錄和入口檔案index.html在public目錄中。非釋出分支.gitignore檔案會忽略這些檔案,釋出分支去除忽略即可。
- public [公共資源目錄,暴露到全球資訊網]
- ├── dist [前端build之後的資源目錄,build生成的目錄,不是釋出分支忽略該目錄]
- │ └── …
- ├── index.html [前端入口檔案,build生成的檔案,不是釋出分支忽略該檔案]
資料庫物件關係對映
資料庫物件關係對映ORM(Object Relation
Map)是什麼?按照我目前的理解:顧名思義是建立物件和抽象事物的關聯關係,在資料庫建模中model實體類其實就是具體的表,對錶的操作其實就是對model例項的操作。可能絕大多數的人都要問“為什麼要這樣做,直接sql語句操作不好嗎?搞得這麼麻煩!”,我的答案:直接sql語句當然可以,一切都是靈活的,但是從一個專案的
可複用,可維護, 可擴充套件
出發,採用ORM思想處理資料操作是理所當然的,想想如果若干一段時間你看見程式碼裡大段的難以閱讀且無從複用的sql語句,你是什麼樣的心情。
市面上對於ORM的具體實現有thinkphp系列框架的Active Record,yii系列框架的Active
Record,laravel系列框架的Eloquent(據說是最優雅的),那我們這裡言簡意賅就叫ORM了。接著為ORM建模,首先是ORM客戶端實體DB:通過配置檔案初始化不同的db策略,並封裝了運算元據庫的所有行為,最終我們通過DB實體就可以直接運算元據庫了,這裡的db策略目前我只實現了mysql(負責建立連線和db的底層操作)。接著我們把DB實體的sql解析功能獨立成一個可複用的sql解析器的trait,具體作用:把物件的鏈式操作解析成具體的sql語句。最後,建立我們的模型基類model,model直接繼承DB即可。最後的結構如下:
- ├── orm [物件關係模型]
- │ ├── Interpreter.php [sql解析器]
- │ ├── DB.php [資料庫操作類]
- │ ├── Model.php [資料模型基類]
- │ └── db [資料庫類目錄]
- │ └── Mysql.php [mysql實體類]
DB類使用示例
- /**
- * DB操作示例
- *
- * findAll
- *
- * @return void
- */
- public function dbFindAllDemo()
- {
- $where = [
- `id` => [`>=`, 2],
- ];
- $instance = DB::table(`user`);
- $res = $instance->where($where)
- ->orderBy(`id asc`)
- ->limit(5)
- ->findAll([`id`,`create_at`]);
- $sql = $instance->sql;
- return $res;
- }
Model類使用示例
- // controller 程式碼
- /**
- * model example
- *
- * @return mixed
- */
- public function modelExample()
- {
- try {
- DB::beginTransaction();
- $testTableModel = new TestTable();
- // find one data
- $testTableModel->modelFindOneDemo();
- // find all data
- $testTableModel->modelFindAllDemo();
- // save data
- $testTableModel->modelSaveDemo();
- // delete data
- $testTableModel->modelDeleteDemo();
- // update data
- $testTableModel->modelUpdateDemo([
- `nickname` => `easy-php`
- ]);
- // count data
- $testTableModel->modelCountDemo();
- DB::commit();
- return `success`;
- } catch (Exception $e) {
- DB::rollBack();
- return `fail`;
- }
- }
- //TestTable model
- /**
- * Model操作示例
- *
- * findAll
- *
- * @return void
- */
- public function modelFindAllDemo()
- {
- $where = [
- `id` => [`>=`, 2],
- ];
- $res = $this->where($where)
- ->orderBy(`id asc`)
- ->limit(5)
- ->findAll([`id`,`create_at`]);
- $sql = $this->sql;
- return $res;
- }
服務容器模組
什麼是服務容器?
服務容器聽起來很浮,按我的理解簡單來說就是提供一個第三方的實體,我們把業務邏輯需要使用的類或例項注入到這個第三方實體類中,當需要獲取類的例項時我們直接通過這個第三方實體類獲取。
服務容器的意義?
用設計模式來講:其實不管設計模式還是實際程式設計的經驗中,我們都是強調“高內聚,鬆耦合”,我們做到高內聚的結果就是每個實體的作用都是極度專一,所以就產生了各個作用不同的實體類。在組織一個邏輯功能時,這些細化的實體之間就會不同程度的產生依賴關係,對於這些依賴我們通常的做法如下:
- class Demo
- {
- public function __construct()
- {
- // 類demo直接依賴RelyClassName
- $instance = new RelyClassName();
- }
- }
這樣的寫法沒有什麼邏輯上的問題,但是不符合設計模式的“最少知道原則”,因為之間產生了直接依賴,整個程式碼結構不夠靈活是緊耦合的。所以我們就提供了一個第三方的實體,把直接依賴轉變為依賴於第三方,我們獲取依賴的例項直接通過第三方去完成以達到鬆耦合的目的,這裡這個第三方充當的角色就類似系統架構中的“中介軟體”,都是協調依賴關係和去耦合的角色。最後,這裡的第三方就是所謂的服務容器。
在實現了一個服務容器之後,我把Request,Config等例項都以單例的方式注入到了服務容器中,當我們需要使用的時候從容器中獲取即可,十分方便。使用如下:
- // 注入單例
- App::$container->setSingle(`別名,方便獲取`, `物件/閉包/類名`);
- // 例,注入Request例項
- App::$container->setSingle(`request`, function () {
- // 匿名函式懶載入
- return new Request();
- });
- // 獲取Request物件
- App::$container->getSingle(`request`);
Nosql模組
提供對nosql的支援,提供全域性單例物件,藉助我們的服務容器我們在框架啟動的時候,通過配置檔案的配置把需要的nosql例項注入到服務容器中。目前我們支援redis/memcahed/mongodb。
如何使用?如下,
- // 獲取redis物件
- App::$container->getSingle(`redis`);
- // 獲取memcahed物件
- App::$container->getSingle(`memcahed`);
- // 獲取mongodb物件
- App::$container->getSingle(`mongodb`);
介面文件生成和介面模擬模組
通常我們寫完一個介面後,介面文件是一個問題,我們這裡使用Api Blueprint協議完成對介面文件的書寫和mock(可用),同時我們配合使用Swagger通過介面文件實現對介面的實時訪問(目前未實現)。
Api Blueprint介面描述協議選取的工具是snowboard,具體使用說明如下:
介面文件生成說明
- cd docs/apib
- ./snowboard html -i demo.apib -o demo.html -s
- open the website, http://localhost:8088/
介面mock使用說明
- cd docs/apib
- ./snowboard mock -i demo.apib
- open the website, http://localhost:8087/demo/index/hello
單元測試模組
基於phpunit的單元測試,寫單元測試是個好的習慣。
如何使用?
tests目錄下編寫測試檔案,具體參考tests/demo目錄下的DemoTest檔案,然後執行:
- vendor/bin/phpunit
測試斷言示例:
- /**
- * 演示測試
- */
- public function testDemo()
- {
- $this->assertEquals(
- `Hello Easy PHP`,
- // 執行demo模組index控制器hello操作,斷言結果是不是等於`Hello Easy PHP`
- App::$app->get(`demo/index/hello`)
- );
- }
Git鉤子配置
目的規範化我們的專案程式碼和commit記錄。
- 程式碼規範:配合使用php_codesniffer,在程式碼提交前對程式碼的編碼格式進行強制驗證。
- commit-msg規範:採用ruanyifeng的commit msg規範,對commit msg進行格式驗證,增強git log可讀性和便於後期查錯和統計log等, 這裡使用了 Treri 的commit-msg指令碼,Thx~。
輔助指令碼
cli指令碼
以命令列的方式執行框架,具體見使用說明。
build指令碼
打包PHP專案指令碼,打包整個專案到runtime/build目錄,例如:
- runtime/build/App.20170505085503.phar
- <?php
- // 入口檔案引入包檔案即可
- require(`runtime/build/App.20170505085503.phar`)
如何使用?
執行:
- composer install
- chmod -R 777 runtime
網站服務模式:
- 步驟 1: yarn install
- 步驟 2: DOMAIN=http://localhost:666 npm run demo
- 步驟 3: cd public
- 步驟 4: php -S localhost:666
訪問網站:http://localhost:666/index.html
訪問介面:http://localhost:666/Demo/Index/hello
demo如下:
客戶端指令碼模式:
- php cli –method= –= …
- 例如, php cli –method=demo.index.get –username=easy-php
獲取幫助:
使用命令 php cli 或者 php cli –help
問題和貢獻
不足的地方還有很多,如果大家發現了什麼問題,可以給我提 issue 或者PR。
或者你覺著在這個框架實現的細節你想了解的,一樣可以給我提 issue ,後面我會總結成相應的文章分享給大家。
如何貢獻?
- cp ./.git-hooks/* ./git/hooks
然後正常發起PR即可, 所有的commit我都會進行程式碼格式(psr)驗證和commit-msg驗證,如果發生錯誤,請按照提示糾正即可。
專案地址: https://github.com/TIGERB/easy-php
TODO
- 懶載入優化框架載入流程
- 效能測試和優化
- 變更Helper助手類的成員方法為框架函式,簡化使用提高生產效率
- 提供更友善的開發api幫助
- 模組支援資料庫nosql自定義配置
- 支援mysql主從配置
- ORM提供更多鏈式操作api
- 框架log行為進行級別分類
- 想辦法解決上線部署是配置檔案問題
- 基於phar檔案和git webhook自動化打包部署
- …
相關文章
- 從 0 開始構建一個屬於你自己的 PHP 框架PHP框架
- 用開源元件構建屬於你的 PHP 框架元件PHP框架
- 構建屬於自己的Flutter混合開發框架Flutter框架
- 從零開始構建自己的第一個vue專案Vue
- 構建自己的 PHP 框架PHP框架
- 從零開始搭建屬於自己的網站網站
- 從0開始構建一個瀚高資料庫Docker映象資料庫Docker
- VsCode從零開始配置一個屬於自己的Vue開發環境VSCodeVue開發環境
- 從零開始打造自己的PHP框架――第2章PHP框架
- 授予漁,從0開始搭建一個自己想要的網頁網頁
- 實現一個屬於自己的React框架(一)React框架
- 從 0 開始構建知識圖譜的 5 個啟動建議
- 從今天開始,拿起VuePress打造屬於自己的專屬部落格Vue
- 酷!一鍵構建我自己的PHP框架的開發環境PHP框架開發環境
- 從零開始編寫自己的JavaScript框架(一)JavaScript框架
- 從零開始構建一個webpack專案Web
- [譯] 如何用 Python 從零開始構建你自己的神經網路Python神經網路
- 從 0 開始手寫一個 Mybatis 框架,三步搞定!MyBatis框架
- 從0開始搭建自己的直播平臺
- 從零開始,打造屬於你的 ChatGPT 機器人!ChatGPT機器人
- 從 0 開始學架構架構
- devops-5:從0開始構建一條完成的CI CD流水線dev
- 構建屬於自己的 Linux 發行版Linux
- node 構建屬於自己的包,以及釋出
- Flutter從0開發一個路由框架Flutter路由框架
- 如何從 0 開始學一門新技術框架框架
- JavaWeb——從零開始構建一個客戶管理系統(一)JavaWeb
- 從零開始編寫自己的JavaScript框架(二)JavaScript框架
- vuePress從零開始搭建自己專屬的文件集合Vue
- 深入淺出Spring Web MVC:從零開始構建你的第一個Web應用SpringWebMVC
- 【Fizzday前言】使用composer構建自己的php框架PHP框架
- 如何開發屬於自己的第一個Java程式Java
- 如何開發屬於自己的第一個Java程式?Java
- 如何從零開始學習一個框架框架
- 從0開始寫一個基於Flutter的開源中國客戶端(5)——App整體佈局框架搭建Flutter客戶端APP框架
- 用C++從0開始開發自己的程式語言C++
- 從零開始 實現一個自己的指令碼引擎指令碼
- 從零開始實現一個自己的指令碼引擎指令碼