Composer Repositories / 從私有套件、Fork 修復到 Monorepo 本地開發
在 PHP 開發中,Packagist.org 是我們最熟悉的公開套件中心。只要下達 composer require vendor/package,Composer 就會自動替我們拉取並解析相依性。
但在企業內部專案或高階架構實務中,我們經常面臨三種無法只靠公開 Packagist 解決的場景:
- 企業私有套件:公司內部的核心業務邏輯、通用 SDK 或共用模組,不能對外公開發佈到 Packagist。
- 開源套件緊急修復(Hotfix Fork):第三方套件出現重大漏洞或 Bug,而官方尚未合併 Pull Request,必須先臨時改用自己 Fork 的修復分支。
- 本地套件模組化開發(Local / Monorepo):正在抽離一個共用套件,希望在主專案即時除錯,不想每改一行程式碼就
git commit、git push再更新。
這些需求都可以透過 composer.json 中的 repositories 區塊優雅解決。
一、三種最核心的自訂套件庫類型
1. VCS(Version Control System,版本控制系統)
這是最常見的模式。只要你的私有套件放在 Git、GitLab、GitHub 或 Bitbucket 上,即使沒有發佈到 Packagist,也能直接當作套件來源。
步驟 1:定義私有套件本身的 composer.json
在你的私有倉庫根目錄建立 composer.json,必須定義唯一的 name:
{
"name": "my-company/payment-sdk",
"description": "Company Internal Payment SDK",
"autoload": {
"psr-4": {
"MyCompany\\Payment\\": "src/"
}
}
}
步驟 2:在主專案引入該私有倉庫
在主專案的 composer.json 加入 repositories 設定:
{
"repositories": [
{
"type": "vcs",
"url": "[email protected]:my-company/payment-sdk.git"
}
],
"require": {
"my-company/payment-sdk": "^1.0"
}
}
Composer 如何判定版本號?
- 正式發布(Tags):Composer 讀取版本的首要依據是 Git 的 Tag(例如
v1.0.0或1.0.0會被對應為版本號1.0.0)。- 開發分支(Branches):若指向分支,前綴必須加上
dev-。例如main分支對應版本為dev-main。
2. Path(本地路徑與 Monorepo 開發利器)
如果你正在本地同時開發主專案與共用套件,每次微調套件都要推上 Git 再 composer update 會極為耗時。
type: "path" 允許你直接指定本地檔案目錄,Composer 會自動建立軟連結(Symlink) 到 vendor/ 目錄中,實現程式碼即時生效:
{
"repositories": [
{
"type": "path",
"url": "../packages/payment-sdk",
"options": {
"symlink": true
}
}
],
"require": {
"my-company/payment-sdk": "@dev"
}
}
- 優點:修改
../packages/payment-sdk裡面的任何一行程式碼,主專案立刻生效,無須重新執行 composer。 - 注意:本地套件的目錄底下同樣必須具備有效的
composer.json。
3. Package(封裝無 composer.json 的第三方函式庫)
偶爾我們會遇到歷史悠久的第三方套件、或者未曾支援 Composer 的原生 JS/PHP 函式庫。此時可以使用 type: "package",直接在主專案的 composer.json 中替它定義版本與載入規則:
{
"repositories": [
{
"type": "package",
"package": {
"name": "legacy/old-library",
"version": "2.1.0",
"dist": {
"url": "https://example.com/downloads/old-lib-2.1.0.zip",
"type": "zip"
},
"autoload": {
"classmap": ["lib/"]
}
}
}
],
"require": {
"legacy/old-library": "2.1.0"
}
}
二、實戰密技:Fork 開源套件進行緊急修復(Branch Alias)
假設你在專案中使用的 guzzlehttp/guzzle: ^7.4 遇到一個重大 Bug,你已在 GitHub 上 Fork 並在名為 fix-timeout-bug 的分支上修復,但官方尚未將 PR 合併。
這時若直接將版本改為 "dev-fix-timeout-bug",很可能會因為其他依賴套件要求 guzzlehttp/guzzle: ^7.4 而導致相依性解析崩潰(Dependency Conflict)。
解決方法:使用 Inline Branch Alias(行內別名)欺騙解析器:
{
"repositories": [
{
"type": "vcs",
"url": "[email protected]:my-username/guzzle.git"
}
],
"require": {
"guzzlehttp/guzzle": "dev-fix-timeout-bug as 7.4.5"
}
}
這行指令告訴 Composer:「請拉取我的 fix-timeout-bug 分支,但在滿足其他套件的相依條件時,請將它視為 7.4.5 版本」。這能完美化解版本相依衝突,待日後官方釋出更新後,只需移除 repositories 即可無縫切回官方版。
三、CI/CD 與自動化部署中的認證處理
在 Docker、GitHub Actions 或 GitLab CI 等自動化環境中,Composer 存取私有倉庫通常會遇到權限問題。推薦兩種解法:
1. SSH Deploy Key(推薦)
在 CI 伺服器注入具備私有倉庫讀取權限的 SSH 私鑰,並在 repositories 中使用 SSH 格式([email protected]:...)。
2. Personal Access Token(搭配 auth.json)
若使用 HTTPS 網址,可透過獨立的 auth.json 提供 Token,避免將敏感憑據寫死在 composer.json 中:
# 本地生成或由 CI 注入
composer config --global github-oauth.github.com <YOUR_TOKEN>
在 CI 環境中,更推薦直接注入環境變數 COMPOSER_AUTH:
export COMPOSER_AUTH='{"github-oauth": {"github.com": "ghp_xxxxxxxxxxxx"}}'
四、Composer 2 的現代優化:效能加速與防禦供應鏈攻擊
早期的 Composer 1 在定義多個自訂倉庫時,搜尋速度非常緩慢,因為它會對每個倉庫逐一請求所有中繼資料(Metadata)。
在 Composer 2 中,官方引入了極具價值的套件來源過濾功能(only 與 exclude):
{
"repositories": [
{
"type": "vcs",
"url": "[email protected]:my-company/payment-sdk.git",
"only": ["my-company/payment-sdk"]
}
]
}
為什麼這非常重要?
- 大幅提升建置速度:Composer 只有在尋找
my-company/payment-sdk時才會連線到該私有庫,解析其他上百個公開套件時會直接略過,建置時間省下數十秒。 - 防禦依賴混淆攻擊(Dependency Confusion):避免攻擊者在公開的 Packagist 上註冊一個同名但惡意更高版本的套件,藉此掉包你內部的私有程式碼。
五、特殊需求:完全關閉 Packagist
在高度受限的封閉內網(Air-gapped Network)或金融機構中,若所有套件都必須經由自建的內部套件鏡像(如 Satis 或 Private Packagist),可以直接在設定中停用官方 Packagist:
{
"repositories": [
{
"packagist.org": false
},
{
"type": "composer",
"url": "https://satis.internal.company.com"
}
]
}
結語
自訂套件庫是從一般應用開發跨入企業級架構與模組化維護的關鍵一步。掌握 VCS 的版本對應邏輯、Path 的本地軟連結特性、Branch Alias 的修復欺騙技法,以及 Composer 2 的來源過濾,就能讓團隊在管理程式庫時兼具開發彈性與系統穩定。