各位观众老爷,晚上好!今天咱们聊点儿“骚”的,啊不,是“潮”的——PHP Monorepo架构,看看怎么用Composer和Lerna把咱们的代码玩出新花样!
开场白:单身久了,看什么都像对象?
话说,程序员的世界里,代码就像自己的孩子,辛辛苦苦写出来,总想好好呵护。但项目多了,代码散落在各处,就像单身久了,看什么都像对象,想找个靠谱的都难。
传统的代码管理方式,每个项目一个仓库(Repo),叫做Multi-Repo。优点是独立性强,改动互不影响。缺点嘛,就像各自为战的游击队,资源分散,依赖管理混乱,简直是噩梦。
这时候,Monorepo就像一个温柔的港湾,把所有代码都放在一起,集中管理,统一构建,仿佛一个大家庭,其乐融融。
第一幕:Monorepo是个啥?
Monorepo,字面意思就是“单一代码仓库”。它是一种代码管理策略,将多个项目、组件或库的代码放在同一个版本控制仓库中。这和Multi-Repo(多仓库)模式形成鲜明对比,Multi-Repo每个项目都有自己的独立仓库。
Monorepo的优点:
- 代码复用性高: 就像一家人,互相借东西方便得很,代码复用起来也更容易,避免重复造轮子。
- 依赖管理简单: 集中管理依赖关系,升级、修复bug更方便,再也不用担心依赖冲突了。
- 原子性变更: 一个变更可以影响多个项目,保证一致性,避免出现“按下葫芦浮起瓢”的情况。
- 协作效率高: 团队成员可以更容易地了解整个项目的结构和代码,协作起来更流畅。
Monorepo的缺点:
- 仓库体积大: 所有代码都在一个仓库里,体积肯定不小,clone、checkout可能会慢一些。
- 构建复杂: 需要更复杂的构建流程来管理各个项目之间的依赖关系。
- 权限管理: 需要更精细的权限管理,避免误操作影响其他项目。
第二幕:Composer登场!PHP世界的依赖管家
Composer,PHP的依赖管理工具,相当于Java的Maven,JavaScript的npm。它可以帮助我们管理项目中的各种依赖包,自动下载、安装、更新。
Composer的基本使用:
-
安装Composer:
curl -sS https://getcomposer.org/installer | php mv composer.phar /usr/local/bin/composer composer --version
-
创建
composer.json
文件:{ "name": "my-project", "description": "My awesome project", "require": { "monolog/monolog": "^2.0" }, "autoload": { "psr-4": { "MyProject\": "src/" } } }
name
: 项目名称description
: 项目描述require
: 依赖包列表autoload
: 自动加载配置
-
安装依赖:
composer install
-
更新依赖:
composer update
Composer在Monorepo中的应用:
在Monorepo中,每个项目/package都有自己的composer.json
文件,用于声明自己的依赖。可以通过Composer Workspaces功能,实现更高效的依赖管理。
Composer Workspaces:
Composer Workspaces允许在一个项目中管理多个包,并共享依赖关系。这对于Monorepo来说非常有用。
-
根目录的
composer.json
:{ "name": "my-monorepo", "description": "My awesome monorepo", "require": { "php": "^7.4 || ^8.0" }, "autoload": { "psr-4": { "MyMonorepo\": "src/" } }, "repositories": [ { "type": "path", "url": "packages/*" } ], "extra": { "composer-exit-on-patch": true, "patchLevel": "minor" } }
repositories
: 声明包的查找路径,这里指向packages
目录下的所有子目录。
-
packages/package-a/composer.json
:{ "name": "my-monorepo/package-a", "description": "Package A", "autoload": { "psr-4": { "MyMonorepo\PackageA\": "src/" } }, "require": { "my-monorepo/package-b": "*" } }
-
packages/package-b/composer.json
:{ "name": "my-monorepo/package-b", "description": "Package B", "autoload": { "psr-4": { "MyMonorepo\PackageB\": "src/" } } }
使用Composer Workspaces后,可以在根目录下执行composer install
,Composer会自动解析所有子项目的依赖关系,并将它们安装到vendor
目录中。
第三幕:Lerna闪亮登场!JavaScript世界的Monorepo神器
Lerna最初是为JavaScript Monorepo设计的工具,但它的一些思想和机制可以借鉴到PHP Monorepo中。Lerna的主要功能是管理多个包的版本发布和依赖关系。
Lerna的主要功能:
- 版本管理: 自动检测哪些包需要发布新版本,并自动更新
package.json
文件。 - 依赖管理: 支持使用
npm
或yarn
来管理依赖关系。 - 发布: 自动发布包到npm仓库。
Lerna在PHP Monorepo中的借鉴:
虽然Lerna是为JavaScript设计的,但我们可以借鉴它的版本管理和依赖管理思路。
- 版本管理: 可以使用Git tag来管理PHP包的版本。当需要发布新版本时,可以创建一个新的Git tag,并更新
composer.json
文件中的版本号。 - 依赖管理: 可以使用Composer Workspaces来管理依赖关系。
- 构建脚本: 可以编写Shell脚本或PHP脚本来自动化构建、测试、发布流程。
一个简单的PHP Monorepo构建脚本:
#!/bin/bash
# 遍历packages目录下的所有子目录
for package_dir in packages/*; do
# 检查是否是目录
if [ -d "$package_dir" ]; then
# 进入子目录
cd "$package_dir"
# 执行构建命令
echo "Building $package_dir..."
composer install
# 执行单元测试
echo "Running tests for $package_dir..."
./vendor/bin/phpunit
# 返回上一级目录
cd ..
fi
done
echo "All packages built and tested successfully!"
第四幕:实战演练!搭建一个简单的PHP Monorepo
咱们来搭建一个简单的PHP Monorepo,包含两个包:package-a
和package-b
。
-
创建目录结构:
my-monorepo/ ├── packages/ │ ├── package-a/ │ │ ├── src/ │ │ │ └── MyClassA.php │ │ ├── composer.json │ │ └── phpunit.xml │ ├── package-b/ │ │ ├── src/ │ │ │ └── MyClassB.php │ │ ├── composer.json │ │ └── phpunit.xml ├── composer.json └── phpunit.xml
-
根目录的
composer.json
:{ "name": "my-monorepo", "description": "My awesome monorepo", "require": { "php": "^7.4 || ^8.0", "phpunit/phpunit": "^9.0" }, "autoload": { "psr-4": { "MyMonorepo\": "src/" } }, "repositories": [ { "type": "path", "url": "packages/*" } ], "config": { "sort-packages": true }, "extra": { "composer-exit-on-patch": true, "patchLevel": "minor" } }
-
packages/package-a/composer.json
:{ "name": "my-monorepo/package-a", "description": "Package A", "autoload": { "psr-4": { "MyMonorepo\PackageA\": "src/" } }, "require": { "my-monorepo/package-b": "*" } }
-
packages/package-b/composer.json
:{ "name": "my-monorepo/package-b", "description": "Package B", "autoload": { "psr-4": { "MyMonorepo\PackageB\": "src/" } } }
-
packages/package-a/src/MyClassA.php
:<?php namespace MyMonorepoPackageA; use MyMonorepoPackageBMyClassB; class MyClassA { public function doSomething() { $b = new MyClassB(); return "A says: " . $b->getMessage(); } }
-
packages/package-b/src/MyClassB.php
:<?php namespace MyMonorepoPackageB; class MyClassB { public function getMessage() { return "Hello from B!"; } }
-
packages/package-a/phpunit.xml
:<?xml version="1.0" encoding="UTF-8"?> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd" bootstrap="vendor/autoload.php" cacheResultFile=".phpunit.cache/test-results" executionOrder="depends,defects" forceCoversAnnotation="true" beStrictAboutCoversAnnotation="true" beStrictAboutOutputDuringTests="true" beStrictAboutTodoAnnotated="true" verbose="true"> <testsuites> <testsuite name="Package A Test Suite"> <directory suffix="Test.php">./tests</directory> </testsuite> </testsuites> <coverage processUncoveredFiles="true"> <include> <directory suffix=".php">./src</directory> </include> </coverage> <php> <ini name="error_reporting" value="-1"/> </php> </phpunit>
-
packages/package-b/phpunit.xml
:内容类似
packages/package-a/phpunit.xml
,只需要修改testsuite name
即可。 -
在根目录下执行
composer install
。 -
编写单元测试,验证代码的正确性。
第五幕:构建优化!让Monorepo飞起来
Monorepo的构建过程可能会比较慢,尤其是在大型项目中。我们需要采取一些优化措施来提高构建速度。
-
并行构建: 可以使用
xargs
或parallel
等工具来并行执行构建任务。find packages -name composer.json -print0 | xargs -0 -n 1 -P 4 composer install
-P 4
: 指定并行执行的任务数量。
-
缓存: 可以使用Composer的缓存机制来避免重复下载依赖包。
composer config --global cache-dir /path/to/global/cache
-
增量构建: 只构建修改过的包及其依赖。可以使用Git来检测哪些包发生了变化。
# 获取上次提交到当前提交之间修改过的目录 changed_dirs=$(git diff --name-only HEAD^ HEAD | xargs -n 1 dirname | sort -u) # 遍历修改过的目录 for dir in $changed_dirs; do # 检查是否是packages目录下的子目录 if [[ $dir == packages/* ]]; then # 进入子目录 cd "$dir" # 执行构建命令 echo "Building $dir..." composer install ./vendor/bin/phpunit # 返回上一级目录 cd ../.. fi done
-
使用Docker: 将构建过程放在Docker容器中,可以保证构建环境的一致性,并利用Docker的缓存机制来加速构建。
第六幕:权衡利弊!Monorepo适合你吗?
Monorepo并非银弹,它也有自己的适用场景。在选择Monorepo架构之前,需要仔细权衡利弊。
特性 | Monorepo | Multi-Repo |
---|---|---|
代码复用性 | 高 | 低 |
依赖管理 | 简单 | 复杂 |
原子性变更 | 支持 | 不支持 |
协作效率 | 高 | 低 |
仓库体积 | 大 | 小 |
构建复杂性 | 高 | 低 |
权限管理 | 复杂 | 简单 |
适用场景 | 大型项目,多个组件/库之间存在依赖 | 小型项目,项目之间独立性强 |
总结:
Monorepo是一种先进的代码管理策略,可以提高代码复用性、简化依赖管理、提高协作效率。但它也需要更复杂的构建流程和权限管理。在选择Monorepo架构之前,需要仔细评估项目的需求和团队的实力。
希望今天的讲座能给大家带来一些启发,让大家在代码管理的道路上越走越远,越走越宽广!下课!