PHP後端組織項目結構的思考

這是 後端開發者從零作一個移動應用 的後端部分第二篇。介紹下一個新項目,後端該如何從零去搭建。咱們先假設這個項目由兩部組成php

  • 提供給wap站點、app使用的api;前端

  • 提供給運營人員使用的管理後臺。mysql

整個項目採用 Phalcon,項目的demo能夠 點這裏 參閱git

備註:跟隨文章進度,項目持續更新,最後會與配套的wap app造成一個總體github

項目最終至少會包含如下內容:redis

  • 小米消息推送sql

  • 支付集成(支付寶、招商、微信)json

  • 基於 Codeception 的api測試後端

  • 登錄api(這部分採用oauth2,會基於 'bshaffer/oauth2-server-php' 作)api

項目結構回顧

後端系統通常都是採用 MVC 結構(這裏均以PHP爲例),M 表明模型,V 表明視圖,C 表明控制器。我在囉嗦幾句

Model指的是數據模型,這個數據模型包括你的Mysql中的表結構,或者redis的緩存對象結構均可以。它表明一個數據操做單元。

View指的展現給用戶瀏覽、直接操做的界面,這個你們都懂,很少說

Controller 控制器,主要是爲了隔離 View 與 Model 直接打交道,他作爲一箇中間人,兩頭傳遞小紙條。

在我過往的項目中,我主要的困惑在於,業務邏輯是放在 C 仍是放在 M。

從對象角度出發,業務邏輯無非就是操做數據,要麼讀取,要麼修改,那麼應該放在M層,由於一個對象應該有本身的屬性與方法。

業務放在M中

實際工做中咱們經常有這樣的場景,好比:讀取一個遊戲列表數據,數據包括遊戲的詳情以及遊戲的版本信息以及下載信息。由於遊戲app會存在升級,所以一個遊戲會對應多個包。那麼這裏至少存在兩個model

  • 遊戲詳情model,包括遊戲的名稱,logo等基本信息

  • 遊戲的包信息model,包括包所屬平臺,大小,下載地址,版本信息等

那麼這個動做的方法應該封裝在哪裏呢?之前的作法是,分別封裝對應的操做到對應的model,而後在控制器中分別調用。說回到這裏,遊戲model封裝了查詢遊戲列表的method,而後包model封裝了根據遊戲id查詢包信息的method。

而後咱們在控制器中分別調用這個兩個方法,而後再進行組裝,把遊戲對應的包設置到對應的遊戲中。

那麼有一個問題,假設咱們在遊戲詳情這個控制器方法中,須要返回一個相關遊戲的集合,難道又重複一次上面的操做?
有人會說把處理遊戲部分抽離成一個公共方法,那麼假設是要在新聞詳情裏邊調用呢?這根本不應在同一個控制器裏邊啊!

業務放在C中

上面咱們把方法放在model中遇到了複用的小麻煩,那麼繼續看看放到controller中會怎樣?

這個時候的一個好處是:咱們可使用鏈接查詢,將剛剛的2次查詢,經過鏈接查詢1次完成,對於mysql的時間減小了,程序性能提高,而後對查詢結果啪啪啪處理完成。

好吧,不日後面說了,相信你們已經發現了,這個查詢過程仍是不可複用。天然而然的,咱們這裏應該想到,將它提煉成一個方法,沒法知足其餘控制器使用(一個控制器調用另一個控制器的想法想都別想啊)。那麼只能提煉成一個類了,這個類來封裝全部的業務。

這樣以後,任何須要遊戲列表數據的地方,直接調用這個GameServer(假設封裝的業務邏輯都放在xxxServer中)就能夠得到相同的數據,而後若是業務變更,咱們也只須要改動這一處,全部地方獲得的數據也將會是一致的。

所以經過回顧,咱們得出咱們的後端項目須要一個server的層次,來存放業務邏輯。

Server層存在的意義

分離出來的這一層,集中涵蓋了全部的業務功能,極大的提升了代碼的複用性,除了不一樣控制器不一樣方法的直接使用,還包括了不一樣模塊之間的複用。

可是在不一樣模塊以前服用,server層也須要考慮一些額外的東西,好比咱們有一個app api模塊,有一個後臺管理模塊。那麼都是獲取列表數據,可能給app api模塊可能不須要某些字段,可是後臺管理須要知悉所有內容,以及後臺用戶權限上的一些問題。這些部分能夠繼續進行拆分,與server組合。須要結合本身的業務來進行管理。

我我的實踐過程當中代碼的另一個好處是,server層從某種層度上讓C層變得簡單,這讓團隊中的新人可以快速上手接觸代碼。好比小明是團隊新人,那麼在他熟悉所使用框架的前提下,他能夠馬上在C層開始作事情,由於這裏沒有業務,有的只是驗證客戶端傳過來的數據,以及對server層的調用返回。經過這個過程能夠加速其融入團隊的進程。

統一的返回格式

約定api返回的數據格式,這基本上是系統開發開始的第一步,原先經常使用的方式就是在每一個控制器中經過

return json_encode([
    'msg' => 'ok',// 攜帶的信息,能夠用來前端 alert 提示用戶
    'data' => [// 具體數據
        ... ...
    ],
    'code' => '0', // 0表示成功,其餘表示對應錯誤
])

那麼這裏首先遇到的第一個問題,爲了簡化前端對類型的判斷,基本上全部的字段值,都是返回字符串形式。那麼 data 裏邊的內容就須要在每一個控制器中進行處理字符串、utf-8編碼等問題。要重複代碼,就算你抽離成一個方法,也須要面對該問題。好點的解決方案是在返回數據的攔截器(每個框架都有相似的概念)內進行統一的處理。

像上面這樣的代碼寫法,帶來的額外問題可能有,字段名稱打錯,好比: code 寫成 cdoe ,data 寫成 date。爲程序代碼額外的風險(尤爲是bug修復時最容易出現該狀況)

那麼一種解決辦法就該由此想到,採用對象的方式來規範化返回的數據結構。好比咱們定義一個類:

class ResultData {
    
    /**
     * 返回的信息提示
     * @var string $msg
     */
    private $msg;

    /**
     * 返回的數據結構
     * @var array|object|string
     */
    private $data;
    
    /**
     * api 狀態碼
     * @var int $apiCode
     * @see ApiCode
     */
    private $apiCode;
    
    public function __construct(int $apiCode, string $msg = 'ok', $data = null)
    {
        $this->apiCode = strval($apiCode);
        $this->msg = trim(strval($msg));
        $this->data = $data;
    }
    
    /**
     * 獲取數據結果
     * @return array
     */
    public function getRetData()
    {
        if (! is_array($this->data) && is_object($this->data) && method_exists($this->data, 'toArray')) {
            $this->data = $this->data->toArray();
        }

        // valueToString 將data的value轉化爲 string 而且作utf-8轉碼
        $result = [
            'code' => $this->apiCode,
            'msg' => $this->msg,
            'data' => $this->data ? ArrayUtil::valueToString($this->data) : [],
        ];

        if (! APP_ENV_PROD) {// 測試環境顯示 api 的處理時間信息 方便優化
            $result['use_time'] = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
        }

        return $result;
    }
}

有了上面這個類,咱們全部的服務層或者controller都應該用它做爲返回值。而後在攔截器中統一進行json encode便可。這樣子即減小了犯錯的可能性,同時對統一處理數據的地方作了統一管理集中到 ResultData 中,那麼之後有什麼特殊變更,調整一處,到處生效。

其它問題

另外還有關於 oauth2 如何集成到項目中等等問題,這部分均放到 x-api 項目中進行說明,紙上說來終覺淺嘛。

日誌的記錄也是系統開發很是重要的部分,這部分沒什麼太多說的,用規範的格式,存儲指定的數據(介質能夠是:db、file)。

系統開發中應該拒絕使用 var_dumpecho 這些方式進行調試,另外建議採用:PhpStorm IDE來進行系統開發。

後續分享

接下來會完善一個 x-api 的基本結構,以及php自動化測試部分文檔教程,而後後端部分就告一段落。(本系列的分享主要集中在代碼層面,不涉及相關係統部署問題)


GitHub:https://github.com/helei112g

相關文章
相關標籤/搜索