Swoft| Swoft 框架組件化改造

date: 2018-3-21 13:22:16
title: Swoft| Swoft 框架組件化改造
description: Swoft 框架從單體應用到組件化改造的架構升級之路php

通過一年多的開發, Swoft 框架功能愈來愈完善, 也愈來愈複雜. 初創時期的 單體應用, 已經沒法支撐項目的快速發展, 因而開發組在年前爲 1.0-beta 版制定了 組件化改造 的重構方案.css

內容速覽:html

  • 組件化原理: PHP 包管理基礎知識
  • 組件化方案: 來自 laravel/symfony 等成熟框架的組件化實現方案
  • Swoft 框架組件化實現

組件化原理

編程始終要解決的一個問題: 代碼複用. 好的代碼, 基本要求是 正確, 能拿到預期的結果, 少 bug. 語言層的代碼複用解決方案, 一般稱之爲 包管理(或者 依賴管理). 流行的編程語言, 都提供了很好的工具鏈對包管理的支持:java

  • 一個命令行工具, 用來 獲取/管理 包, 好比 php 的 composer, python 的 pip, js 的 npm, java 的 maven, go 的 go get
  • 一個包管理的配置文件, 用來講明須要用到(依賴)的包, 好比 PHP 中 composer 使用 composer.josn, js 的 npm 使用 package.json
  • 一個瀏覽包的網站, 用來查看包的信息, 好比 php 的 packgist, python 的 pypi 等

這樣, 當咱們須要不一樣功能的時候, 就能夠去查看是否有包已經提供了相似功能, 不用重複造輪子, 站在巨人的肩膀上.python

回到 PHP 中, PHP中的包管理是如何實現的呢?laravel

  • 命名空間

首先須要知道的一個基礎概念, 是 命名空間. 引入命名空間是爲了 解決同名衝突 -- 2 個包中有名字相同的類, 同時使用時就會出現類重複定義的提示. 使用命名空間後, 由於不一樣的包有不一樣的命名空間, 就不會出現衝突.git

// 若是須要在同一個文件中使用相同名字的類, 使用別名
use A\Far;
use B\Far as BFar;
  • 自動加載 & PSR4

第二個須要知道的基礎概念, 是 自動加載. PHP 中最基礎(或者說最原始)的複用代碼的方法: include/include_one/require/require_once. 不過得益於 PHP 的 SPL庫 中的 spl_autoload_register() 方法, 如今有了更優雅的方式來複用代碼 -- 自動加載. 自動加載的規範也經歷了一段時間的升級與打磨, 最新的是 PSR4標準.github

關於自動加載, 有一個很好的教程: 5-1 SPL使用spl_autoload_register函數裝載類 (10:03)

composer 中的包管理

瞭解了基礎知識後, 就能夠來掌握工具怎麼用了. composer 中的包管理 根據 composer.json 文件中的 autoload / require / require-dev 配置項來管理.web

autoload 定義自動加載, 項目自身的代碼, 也應該按照包管理的規範, 進行組織, 好比 Swoft 的 composer.json 配置文件:redis

...
    "autoload": {
        "psr-4": {
            "App\\": "app/"
        },
        "files": [
            "app/Swoft.php"
        ]
    },
...

composer 支持多種方式的自動加載方式, 這裏面有必定的歷史緣由, 由於須要兼容一些 陳舊 的代碼. 如今比較經常使用的 2 種方式:

  • psr-4: PSR4 標準, 優先推薦的方式
  • files: 直接加載文件, 一般用來加載 幫助函數, 相似於 PHP 的 require 語法來代碼複用

require 標識須要依賴的包, 格式是 包名 - 版本限制 的鍵值對:

...
    "require": {
        "php": ">=7.0",
        "swoft/framework": "^1.0",
        "swoft/rpc": "^1.0",
        "swoft/rpc-server": "^1.0",
        "swoft/rpc-client": "^1.0",
        "swoft/http-server": "^1.0",
        "swoft/http-client": "^1.0",
        "swoft/task": "^1.0",
        "swoft/http-message": "^1.0",
        "swoft/view": "^1.0",
        "swoft/db": "^1.0",
        "swoft/cache": "^1.0",
        "swoft/redis": "^1.0",
        "swoft/console": "^1.0",
        "swoft/devtool": "^1.0",
        "swoft/session": "^1.0",
        "swoft/i18n": "^1.0",
        "swoft/process": "^1.0",
        "swoft/memory": "^1.0",
        "swoft/service-governance": "^1.0"
    },
...

關於 版本控制 的知識, 以及 >= ^ ~ 等特殊字符, alpha beta dev dev-master 等標識, 只是約定俗成的一些定義, 瞭解清楚便可.

require-dev 標識開發環境須要依賴的包, 即正式環境不須要使用到的包, 好比單元測試等:

...
    "require-dev": {
        "eaglewu/swoole-ide-helper": "dev-master",
        "phpunit/phpunit": "^5.7"
    },
...

相似的, 還有 autoload-dev, 表示測試環境下使用到自動加載.

組件化方案: laravel 與 symfony 使用的方案

參考 symfony 中的 composer.json 配置文件laravel 中的 composer.json 配置文件, 會發現裏面有一個配置項: replace.

replace 這個配置項, 在普通項目中很難看到, 倒是組件化改造中的重要配置, 它的定義以下:

使用項目中已有的包, 替換須要依賴的包

好比 symfony 中的 composer.json 配置文件:

...
    "replace": {
        "symfony/asset": "self.version",
        "symfony/browser-kit": "self.version",
        "symfony/cache": "self.version",
        "symfony/config": "self.version",
        "symfony/console": "self.version",
        "symfony/css-selector": "self.version",
        "symfony/dependency-injection": "self.version",
        ...

其中 "symfony/asset" 包, 有一個單獨的github 倉庫 symfony/asset, symfony 項目 自己也包含 "symfony/asset" 包, 使用 replace, symfony 就可使用自身包含的包, 不用去單獨獲取.

這樣帶來的好處:

  • 主包包含全部的子包, 使用時使用 replace 配置, 全部的修改和提交都在主包中進行
  • 其餘項目依舊可使用 require, 單獨使用子包; 子包只接受來自主包分發來的代碼, 不接受在子包上的更改

Swoft 框架組件化實現

Swoft 在 1.0-beta版中的依賴, Swoft 項目:

...
    "require": {
        "php": ">=7.0",
        "swoft/framework": "^1.0",
        "swoft/rpc": "^1.0",
        "swoft/rpc-server": "^1.0",
        "swoft/rpc-client": "^1.0",
        "swoft/http-server": "^1.0",
        "swoft/http-client": "^1.0",
        "swoft/task": "^1.0",
        "swoft/http-message": "^1.0",
        "swoft/view": "^1.0",
        "swoft/db": "^1.0",
        "swoft/cache": "^1.0",
        "swoft/redis": "^1.0",
        "swoft/console": "^1.0",
        "swoft/devtool": "^1.0",
        "swoft/session": "^1.0",
        "swoft/i18n": "^1.0",
        "swoft/process": "^1.0",
        "swoft/memory": "^1.0",
        "swoft/service-governance": "^1.0"
    },
...

改造後, Swoft 項目, 主項目只用依賴 "swoft/framework":

...
    "require": {
        "php": ">=7.0",
        "swoft/framework": "^1.0"
    },
...

"swoft/framework" 項目, 包含其餘子包:

...
    "replace": {
        "swoft/rpc": "self.version",
        "swoft/rpc-server": "self.version",
        "swoft/rpc-client": "self.version",
        "swoft/http-server": "self.version",
        "swoft/http-client": "self.version",
        "swoft/task": "self.version",
        "swoft/http-message": "self.version",
        "swoft/view": "self.version",
        "swoft/db": "self.version",
        "swoft/cache": "self.version",
        "swoft/redis": "self.version",
        "swoft/console": "self.version",
        "swoft/devtool": "self.version",
        "swoft/session": "self.version",
        "swoft/i18n": "self.version",
        "swoft/process": "self.version",
        "swoft/memory": "self.version",
        "swoft/service-governance": "self.version"
    }
...

其中子項目聲明到主項目提交修改:

整個開發流程以下:

  • daydaygo/swoft-framework 項目 新建 component2 分支開發這次組件化改造
  • 修改 Swoft 項目的 composer.json 文件, 快速獲取全部 Swoft 組件的 master 分支代碼:
"require": {
        "php": ">=7.0",
        "swoft/framework": "dev-master",
        "swoft/rpc": "dev-master",
        "swoft/rpc-server": "dev-master",
        "swoft/rpc-client": "dev-master",
        "swoft/http-server": "dev-master",
        "swoft/http-client": "dev-master",
        "swoft/task": "dev-master",
        "swoft/http-message": "dev-master",
        "swoft/view": "dev-master",
        "swoft/db": "dev-master",
        "swoft/cache": "dev-master",
        "swoft/redis": "dev-master",
        "swoft/console": "dev-master",
        "swoft/devtool": "dev-master",
        "swoft/session": "dev-master",
        "swoft/i18n": "dev-master",
        "swoft/process": "dev-master",
        "swoft/memory": "dev-master",
        "swoft/service-governance": "dev-master"
    },
  • 複製各個組件的代碼到 swoft-framework 項目中, 修改 composer.json 的中的 autoload / replace 配置(具體修改點擊連接查看)

Swoft 各組件依賴關係圖: http://naotu.baidu.com/file/7...

  • 提交 swoft-framework 代碼.

下面以推送 swoft-view 組件到對應倉庫中爲例:

出於 github 網速的緣由, 測試過程使用 gitee 來加速

推送子項目到相應的 github 倉庫中, 參考:

# 創建 gitee.com:daydaygo/swoft-framework 倉庫
git remote add gitee git@gitee.com:daydaygo/swoft-framework.git
git push gitee component2

# 拆分
git subsplit init git@gitee.com:daydaygo/swoft-framework.git
# 更多項目, 一次填寫便可
git subsplit publish --heads="component2" --no-tags view:git@gitee.com:daydaygo/swoft-view.git
# 清除生成的臨時文件
rm .subsplit

這個拆分過程耗時較長, 拆分後的效果: gitee.com/daydaygo/swoft-view, gitee.com/daydaygo/swoft-framework

能夠經過添加 github webhook 來作自動化, 具體請參考: dflydev/dflydev-git-subsplit-github-webhook

最後, 測試拆分後的代碼:

  • 修改 swoft 項目的 composer.json 文件, 使用新版的 swoft-framework 項目
...
  // 如今只須要依賴 swoft/framework, 版本號要制定分支, composer 會默認給分支名帶上 dev- 前綴
  "require": {
    "php": ">=7.0",
    "swoft/framework": "dev-component2"
  },
....
  "repositories": {
    "packagist": {
      "type": "composer",
      "url": "https://packagist.phpcomposer.com"
    },
    // 制定包的地址, 這裏指向個人 giee 倉庫地址
    "0": {
      "type": "vcs",
      "url": "https://gitee.com/daydaygo/swoft-framework"
    }
  }
...
# 刪除之前的依賴
rm -rf composer.lock vendor
# 更新
composer install --no-dev

至此, 大功告成.

寫在最後

對項目進行組件化拆分, 推送子包到不一樣 github 倉庫 這樣的需求, 也許只有寫一個大型框架纔會遇到. 但這也是正是動手寫一個框架的樂趣所在. PHP 中的包管理的基礎知識一直感受 遊刃有餘, 直到遇到新的問題, 提出新的挑戰, 才發現還有更多的天地. 願你也能感覺到這分技術的樂趣.

相關文章
相關標籤/搜索