從0開始構建一個屬於你自己的PHP框架

行者武松發表於2017-08-01

如何構建一個自己的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           [資料庫配置]
│    ├── swoole.php             [swoole配置]
│    └── nosql.php              [nosql配置]
docs                            [介面文件目錄]
├── apib                        [Api Blueprint]
│    └── demo.apib              [介面文件示例檔案]
├── swagger                     [swagger]
framework                       [Easy PHP核心框架目錄]
├── exceptions                  [異常目錄]
│      ├── CoreHttpException.php[核心http異常]
├── handles                     [框架執行時掛載處理機制類目錄]
│      ├── Handle.php           [處理機制介面]
│      ├── EnvHandle.php        [環境變數處理機制類]
│      ├── ErrorHandle.php      [錯誤處理機制類]
│      ├── ExceptionHandle.php  [未捕獲異常處理機制類]
│      ├── ConfigHandle.php     [配置檔案處理機制類]
│      ├── NosqlHandle.php      [nosql處理機制類]
│      ├── LogHandle.php        [log機制類]
│      ├── UserDefinedHandle.php[使用者自定義處理機制類]
│      ├── RouterSwooleHan...   [swoole模式路由處理機制類]
│      └── RouterHandle.php     [路由處理機制類]
├── orm                         [物件關係模型]
│      ├── Interpreter.php      [sql解析器]
│      ├── DB.php               [資料庫操作類]
│      ├── Model.php            [資料模型基類]
│      └── db                   [資料庫類目錄]
│          └── Mysql.php        [mysql實體類]
├── router                      [路由策略]
│      ├── RouterInterface.php  [路由策略介面]
│      ├── General.php          [普通路由]
│      ├── Pathinfo.php         [pathinfo路由]
│      ├── Userdefined.php      [自定義路由]
│      ├── Micromonomer.php     [微單體路由]
│      ├── Job.php              [指令碼任務路由]
│      ├── EasySwooleRouter.php [swoole模式路由策略入口類]
│      └── EasyRouter.php       [路由策略入口類]
├── 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                     [框架應用啟用指令碼]
├── swoole.php                  [swoole模式框架應用啟用指令碼]
frontend                        [前端原始碼和資源目錄]
├── src                         [資源目錄]
│    ├── components             [vue元件目錄]
│    ├── views                  [vue檢視目錄]
│    ├── images                 [圖片]
│    ├── ...
├── app.js                      [根js]
├── app.vue                     [根元件]
├── index.template.html         [前端入口檔案模板]
├── store.js                    [vuex store檔案]
jobs                            [指令碼目錄,寫業務指令碼的地方]
├── demo                        [模組目錄]
│    ├── Demo.php               [指令碼演示檔案]
│    ├── ...
public                          [公共資源目錄,暴露到全球資訊網]
├── dist                        [前端build之後的資源目錄,build生成的目錄,不是釋出分支忽略該目錄]
│    └── ...
├── index.html                  [前端入口檔案,build生成的檔案,不是釋出分支忽略該檔案]
├── index.php                   [後端入口檔案]
├── server.php                  [swoole模式後端入口檔案]
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.example                    [環境變數示例檔案]
.gitignore                      [git忽略檔案配置]
.travis.yml                     [持續整合工具travis-ci配置檔案]
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檔案]
run                             [快速開始指令碼]
webpack.config.js               [webpack配置檔案]
yarn.lock                       [yarn lock檔案]

複製程式碼

生命週期

從0開始構建一個屬於你自己的PHP框架

框架模組說明:

入口檔案

定義一個統一的入口檔案,對外提供統一的訪問檔案。對外隱藏了內部的複雜性,類似企業服務匯流排的思想。

// 載入框架執行檔案
require('../framework/run.php');
複製程式碼

[file: public/index.php]

自載入模組

使用spl_autoload_register函式註冊自載入函式到__autoload佇列中,配合使用名稱空間,當使用一個類的時候可以自動載入(require)類檔案。註冊完成自載入邏輯後,我們就可以使用use和配合名稱空間申明對某個類檔案的依賴。

[file: framework/Load.php]

錯誤和異常模組

指令碼執行期間:

  • 錯誤:

通過函式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]

配置檔案模組

載入框架自定義和使用者自定義的配置檔案。

例如,資料庫主從配置.env檔案引數示例:

[database]
dbtype   = mysqldb
dbprefix = easy
dbname   = easyphp
dbhost   = localhost
username = easyphp
password = easyphp
slave    = 0,1

[database-slave-0]
dbname   = easyphp
dbhost   = localhost
username = easyphp
password = easyphp

[database-slave-1]
dbname   = easyphp
dbhost   = localhost
username = easyphp
password = easyphp
複製程式碼

[file: framework/hanles/ConfigHandle.php]

輸入和輸出

  • 定義請求物件:包含所有的請求資訊
  • 定義響應物件:申明響應相關資訊

框架中所有的異常輸出和控制器輸出都是json格式,因為我認為在前後端完全分離的今天,這是很友善的,目前我們不需要再去考慮別的東西。

請求引數校驗,目前提供必傳,長度,數字型別校驗,使用如下
$request = App::$container->get('request');
$request->check('username', 'require');
$request->check('password', 'length', 12);
$request->check('code', 'number');
複製程式碼

[file: framework/Request.php]

[file: framework/Response.php]

路由模組

├── router                      [路由策略]
│      ├── RouterInterface.php  [路由策略介面]
│      ├── General.php          [普通路由]
│      ├── Pathinfo.php         [pathinfo路由]
│      ├── Userdefined.php      [自定義路由]
│      ├── Micromonomer.php     [微單體路由]
│      ├── Job.php              [指令碼任務路由]
│      └── EasyRouter.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 (Framework\App $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->get('request')
);
複製程式碼

實現完成這個gateway之後,我們如何在框架中去使用呢?在logic層目錄中我提供了一個user-defined的實體類,我們把gateway的入口類註冊到UserDefinedCase這個類中,示例如下:

/**
 * 註冊使用者自定義執行的類
 *
 * @var array
 */
private $map = [
    //&emsp;演示 載入自定義閘道器
    'App\Demo\Logics\Gateway\Entrance'
];
複製程式碼

這樣這個gateway就可以工作了。接著說說這個UserDefinedCase類,UserDefinedCase會在框架載入到路由機制之前被執行,這樣我們就可以靈活的實現一些自定義的處理了。這個gateway只是個演示,你完全可以天馬行空的組織你的邏輯~

檢視View去哪了?由於選擇了完全的前後端分離和SPA(單頁應用), 所以傳統的檢視層也因此去掉了,詳細的介紹看下面。

[file: app/*]

使用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生成的檔案,不是釋出分支忽略該檔案]
複製程式碼

[file: frontend/*]

資料庫物件關係對映

資料庫物件關係對映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;
}
複製程式碼

[file: framework/orm/*]

服務容器模組

什麼是服務容器?

服務容器聽起來很浮,按我的理解簡單來說就是提供一個第三方的實體,我們把業務邏輯需要使用的類或例項注入到這個第三方實體類中,當需要獲取類的例項時我們直接通過這個第三方實體類獲取。

服務容器的意義?

用設計模式來講:其實不管設計模式還是實際程式設計的經驗中,我們都是強調“高內聚,鬆耦合”,我們做到高內聚的結果就是每個實體的作用都是極度專一,所以就產生了各個作用不同的實體類。在組織一個邏輯功能時,這些細化的實體之間就會不同程度的產生依賴關係,對於這些依賴我們通常的做法如下:

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->get('request');
複製程式碼

[file: framework/Container]

Nosql模組

提供對nosql的支援,提供全域性單例物件,藉助我們的服務容器我們在框架啟動的時候,通過配置檔案的配置把需要的nosql例項注入到服務容器中。目前我們支援redis/memcahed/mongodb。

如何使用?如下,

// 獲取redis物件
App::$container->getSingle('redis');
// 獲取memcahed物件
App::$container->getSingle('memcahed');
// 獲取mongodb物件
App::$container->getSingle('mongodb');
複製程式碼

[file: framework/nosql/*]

Swoole模式

支援swoole擴充套件下執行

cd public && php server.php
複製程式碼

[file: framework/swoole.php]

Job模式

我們可以在jobs目錄下直接編寫我們的任務指令碼,如下

jobs                            [指令碼目錄,寫業務指令碼的地方]
├── demo                        [模組目錄]
│    ├── Demo.php               [指令碼演示檔案]
│    ├── ...
複製程式碼

任務指令碼示例:

<?php
namespace Jobs\Demo;

/**
 * Demo Jobs
 *
 * @author TIERGB <https://github.com/TIGERB>
 */
class Demo
{
    /**
     * job
     *
     * @example php cli --jobs=demo.demo.test
     */
    public function test()
    {
        echo 'Hello Easy PHP Jobs';
    }
}

複製程式碼

最後直接執行下面的命令即可:

php cli --job=demo.demo.test
複製程式碼

[file: jobs/*]

介面文件生成和介面模擬模組

通常我們寫完一個介面後,介面文件是一個問題,我們這裡使用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
複製程式碼

[file: docs/*]

單元測試模組

基於phpunit的單元測試,寫單元測試是個好的習慣。

如何使用?

tests目錄下編寫測試檔案,具體參考tests/demo目錄下的DemoTest檔案,然後執行:

 vendor/bin/phpunit
複製程式碼

測試斷言示例:

/**
 *&emsp;演示測試
 */
public function testDemo()
{
    $this->assertEquals(
        'Hello Easy PHP',
        // 執行demo模組index控制器hello操作,斷言結果是不是等於'Hello Easy PHP'&emsp;
        App::$app->get('demo/index/hello')
    );
}
複製程式碼

phpunit斷言文件語法參考

[file: tests/*]

Git鉤子配置

目的規範化我們的專案程式碼和commit記錄。

  • 程式碼規範:配合使用php_codesniffer,在程式碼提交前對程式碼的編碼格式進行強制驗證。
  • commit-msg規範:採用ruanyifeng的commit msg規範,對commit msg進行格式驗證,增強git log可讀性和便於後期查錯和統計log等, 這裡使用了Treri的commit-msg指令碼,Thx~。

[file: ./git-hooks/*]

輔助指令碼

cli指令碼

以命令列的方式執行框架,具體見使用說明。

build指令碼

打包PHP專案指令碼,打包整個專案到runtime/build目錄,例如:

runtime/build/App.20170505085503.phar

<?php
// 入口檔案引入包檔案即可
require('runtime/build/App.20170505085503.phar');
複製程式碼

Command:

php cli --build

[file: ./build]

如何使用?

執行:

composer create-project tigerb/easy-php easy --prefer-dist && cd easy

網站服務模式:

快速開始一個demo:

php cli --run
複製程式碼

demo如下:

從0開始構建一個屬於你自己的PHP框架

客戶端指令碼模式:

php cli --method=<module.controller.action> --<arguments>=<value> ...

例如, php cli --method=demo.index.get --username=easy-php
複製程式碼

Swoole模式:

cd public && php server.php
複製程式碼

獲取幫助:

使用命令 php cli 或者 php cli --help

效能-fpm

ab -c 100 -n 10000 "http://easy-php.local/Demo/Index/hello"

Document Path:          /
Document Length:        53 bytes

Concurrency Level:      100
Time taken for tests:   3.259 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      1970000 bytes
HTML transferred:       530000 bytes
Requests per second:    3068.87 [#/sec] (mean)
Time per request:       32.585 [ms] (mean)
Time per request:       0.326 [ms] (mean, across all concurrent requests)
Transfer rate:          590.40 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.3      0       4
Processing:     6   32   4.0     31      68
Waiting:        6   32   4.0     31      68
Total:          8   32   4.0     31      68

Percentage of the requests served within a certain time (ms)
  50%     31
  66%     32
  75%     33
  80%     34
  90%     39
  95%     41
  98%     43
  99%     46
 100%     68 (longest request)
複製程式碼

效能-Swoole

ab -c 100 -n 10000 "http://easy-php.local/Demo/Index/hello"

Concurrency Level:      100
Time taken for tests:   1.319 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      1870000 bytes
HTML transferred:       160000 bytes
Requests per second:    7580.84 [#/sec] (mean)
Time per request:       13.191 [ms] (mean)
Time per request:       0.132 [ms] (mean, across all concurrent requests)
Transfer rate:          1384.39 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    5  10.6      3     172
Processing:     1    9  13.4      7     177
Waiting:        0    7  11.7      6     173
Total:          3   13  16.9     11     179

Percentage of the requests served within a certain time (ms)
  50%     11
  66%     12
  75%     13
  80%     14
  90%     15
  95%     17
  98%     28
  99%     39
 100%    179 (longest request)
複製程式碼

問題和貢獻

不足的地方還有很多,如果大家發現了什麼問題,可以給我提issue或者PR。

或者你覺著在這個框架實現的細節你想了解的,一樣可以給我提issue,後面我會總結成相應的文章分享給大家。

如何貢獻?

cp ./.git-hooks/* ./git/hooks
複製程式碼

然後正常發起PR即可, 所有的commit我都會進行程式碼格式(psr)驗證和commit-msg驗證,如果發生錯誤,請按照提示糾正即可。

專案地址:github.com/TIGERB/easy…

交流群

從0開始構建一個屬於你自己的PHP框架

從0開始構建一個屬於你自己的PHP框架

從0開始構建一個屬於你自己的PHP框架

相關文章