Skip to content

📦 从 Go 管理 PHP 依赖

12 分钟,用 Composer CLI SDK 替代手写 exec.Command 完成 Install / Require / Update / Remove,并拿到结构化的依赖树和过期信息。需要本机已安装 PHP 7.4+ 与 Composer 2.0+。

为什么不用 exec.Command

裸调 composer 二进制要自己拼参数、解析多行文本、处理退出码。Composer CLI SDK 把这些封装成类型化方法,返回 error 或结构体,测试时还能注入 Mock。

第 1 步:创建 Composer 实例

composer.New(composer.DefaultOptions()) 会自动检测本机 Composer;默认开启 AutoInstall,找不到时会自动安装。

go
comp, err := composer.New(composer.DefaultOptions())
if err != nil {
    log.Fatal(err)
}
comp.SetWorkingDir("/path/to/php/project") // 指向含 composer.json 的目录

第 2 步:安装现有依赖

Install(noDev, optimize) 对应 composer install。第一个参数控制是否排除 dev 依赖,第二个控制是否优化自动加载。

第 3 步:增/删/升级

  • RequirePackage(name, version, dev) — 添加依赖并写入 composer.json。
  • Update(packages, noDev) — 升级指定包(空切片表示全量)。
  • Remove(name, dev) — 移除依赖。

第 4 步:拿结构化结果

  • ShowDependencyTree(name) — 返回某包的依赖树文本。
  • GetOutdatedInfo() — 返回 *OutdatedResult,含每个过期包的当前版本、最新版本、升级类型。

完整可运行示例

go
package main

import (
	"fmt"
	"log"

	"github.com/scagogogo/composer-skills/pkg/composer"
)

func main() {
	comp, err := composer.New(composer.DefaultOptions())
	if err != nil {
		log.Fatal(err)
	}
	comp.SetWorkingDir("/tmp/my-php-project")

	// 1️⃣ 安装 composer.lock 里的依赖(不含 dev,优化自动加载)
	if err := comp.Install(false, true); err != nil {
		log.Printf("install: %v", err)
	}

	// 2️⃣ 添加 monolog ^3.0
	if err := comp.RequirePackage("monolog/monolog", "^3.0", false); err != nil {
		log.Fatalf("require: %v", err)
	}

	// 3️⃣ 升级 monolog(传空切片表示只更新列出的包)
	if err := comp.Update([]string{"monolog/monolog"}, false); err != nil {
		log.Printf("update: %v", err)
	}

	// 4️⃣ 看依赖树
	tree, err := comp.ShowDependencyTree("monolog/monolog")
	if err != nil {
		log.Printf("tree: %v", err)
	} else {
		fmt.Println("🌳 依赖树:")
		fmt.Println(tree)
	}

	// 5️⃣ 结构化的过期包列表
	outdated, err := comp.GetOutdatedInfo()
	if err != nil {
		log.Printf("outdated: %v", err)
	} else {
		fmt.Printf("⏳ 过期包数量: %d\n", len(outdated.Installed))
		for _, p := range outdated.Installed {
			fmt.Printf("   - %s: %s%s (%s)\n",
				p.Name, p.Installed, p.Latest, p.LatestStatus)
		}
	}

	// 6️⃣ 移除 monolog
	if err := comp.Remove("monolog/monolog", false); err != nil {
		log.Printf("remove: %v", err)
	}
}

预期输出

🌳 依赖树:
monolog/monolog 3.5.0
├── psr/log 3.0.0
└── ...
⏳ 过期包数量: 2
   - monolog/monolog: 3.5.0 → 3.7.1 (semver-safe-update)
   - psr/log: 3.0.0 → 3.0.2 (semver-safe-update)

进阶:WithOptions 与多包操作

需要传额外 CLI 参数时用 *WithOptions 变体:

go
// 安装时指定 prefer-dist 并跳过脚本
comp.InstallWithOptions(map[string]string{
    "prefer-dist": "",
    "no-scripts":  "",
})

// 一次性 require 多个包
comp.RequireMultiple(map[string]string{
    "monolog/monolog": "^3.0",
    "symfony/console": "^7.0",
}, false)

先试运行再真改

InstallDryRun()UpdateDryRun(packages)RequireDryRun(name, version) 只打印不落盘,适合在 CI 里预演。

工作目录很关键

忘记 SetWorkingDir 会在当前 Go 进程目录执行 composer,多半会报 "composer.json not found"。务必指向真实的 PHP 项目根目录。

下一步

基于 MIT 许可证发布