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 中的包管理的基础知识一直感受 游刃有余, 直到遇到新的问题, 提出新的挑战, 才发现还有更多的天地. 愿你也能感觉到这分技术的乐趣.