date: 2018-3-21 13:22:16
title: Swoft| Swoft 框架組件化改造
description: Swoft 框架從單體應用到組件化改造的架構升級之路php
通過一年多的開發, Swoft 框架功能愈來愈完善, 也愈來愈複雜. 初創時期的 單體應用, 已經沒法支撐項目的快速發展, 因而開發組在年前爲 1.0-beta 版制定了 組件化改造 的重構方案.css
內容速覽:html
編程始終要解決的一個問題: 代碼複用. 好的代碼, 基本要求是 正確, 能拿到預期的結果, 少 bug. 語言層的代碼複用解決方案, 一般稱之爲 包管理(或者 依賴管理). 流行的編程語言, 都提供了很好的工具鏈對包管理的支持:java
go get
composer.josn
, js 的 npm 使用 package.json
這樣, 當咱們須要不一樣功能的時候, 就能夠去查看是否有包已經提供了相似功能, 不用重複造輪子, 站在巨人的肩膀上.python
回到 PHP 中, PHP中的包管理是如何實現的呢?laravel
首先須要知道的一個基礎概念, 是 命名空間. 引入命名空間是爲了 解決同名衝突 -- 2 個包中有名字相同的類, 同時使用時就會出現類重複定義的提示. 使用命名空間後, 由於不一樣的包有不一樣的命名空間, 就不會出現衝突.git
// 若是須要在同一個文件中使用相同名字的類, 使用別名 use A\Far; use B\Far as BFar;
第二個須要知道的基礎概念, 是 自動加載. PHP 中最基礎(或者說最原始)的複用代碼的方法: include/include_one/require/require_once
. 不過得益於 PHP 的 SPL庫 中的 spl_autoload_register()
方法, 如今有了更優雅的方式來複用代碼 -- 自動加載. 自動加載的規範也經歷了一段時間的升級與打磨, 最新的是 PSR4標準.github
關於自動加載, 有一個很好的教程: 5-1 SPL使用spl_autoload_register函數裝載類 (10:03)
瞭解了基礎知識後, 就能夠來掌握工具怎麼用了. 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
, 表示測試環境下使用到自動加載.
參考 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 在 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" } ...
其中子項目聲明到主項目提交修改:
整個開發流程以下:
"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" },
autoload / replace
配置(具體修改點擊連接查看)Swoft 各組件依賴關係圖: http://naotu.baidu.com/file/7...
下面以推送 swoft-view
組件到對應倉庫中爲例:
出於 github 網速的緣由, 測試過程使用 gitee 來加速
推送子項目到相應的 github 倉庫中, 參考:
git subtree
爲 git subplite
命令, 方便使用# 創建 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/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 中的包管理的基礎知識一直感受 遊刃有餘, 直到遇到新的問題, 提出新的挑戰, 才發現還有更多的天地. 願你也能感覺到這分技術的樂趣.