📦 Managing PHP Dependencies from Go
In 12 minutes, use the Composer CLI SDK to replace hand-written exec.Command for Install / Require / Update / Remove, and get structured dependency trees and outdated information. Requires PHP 7.4+ and Composer 2.0+ installed locally.
Why Not Use exec.Command?
Bare composer binary calls require manual argument assembly, multi-line text parsing, and exit code handling. The Composer CLI SDK wraps these into typed methods, returning error or structs, and supports Mock injection for testing.
Step 1: Create Composer Instance
composer.New(composer.DefaultOptions()) auto-detects local Composer; AutoInstall is enabled by default, auto-installing if not found.
comp, err := composer.New(composer.DefaultOptions())
if err != nil {
log.Fatal(err)
}
comp.SetWorkingDir("/path/to/php/project") // Point to directory containing composer.jsonStep 2: Install Existing Dependencies
Install(noDev, optimize) corresponds to composer install. The first parameter controls whether to exclude dev dependencies, the second controls autoloader optimization.
Step 3: Add / Remove / Upgrade
RequirePackage(name, version, dev)— Add dependency and write to composer.json.Update(packages, noDev)— Upgrade specified packages (empty slice means full update).Remove(name, dev)— Remove dependency.
Step 4: Get Structured Results
ShowDependencyTree(name)— Returns dependency tree text for a package.GetOutdatedInfo()— Returns*OutdatedResult, containing current version, latest version, and upgrade type for each outdated package.
Complete Runnable Example
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️⃣ Install dependencies from composer.lock (no dev, optimize autoloader)
if err := comp.Install(false, true); err != nil {
log.Printf("install: %v", err)
}
// 2️⃣ Add monolog ^3.0
if err := comp.RequirePackage("monolog/monolog", "^3.0", false); err != nil {
log.Fatalf("require: %v", err)
}
// 3️⃣ Upgrade monolog (empty slice means only update listed packages)
if err := comp.Update([]string{"monolog/monolog"}, false); err != nil {
log.Printf("update: %v", err)
}
// 4️⃣ View dependency tree
tree, err := comp.ShowDependencyTree("monolog/monolog")
if err != nil {
log.Printf("tree: %v", err)
} else {
fmt.Println("🌳 Dependency tree:")
fmt.Println(tree)
}
// 5️⃣ Structured outdated packages list
outdated, err := comp.GetOutdatedInfo()
if err != nil {
log.Printf("outdated: %v", err)
} else {
fmt.Printf("⏳ Outdated packages: %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️⃣ Remove monolog
if err := comp.Remove("monolog/monolog", false); err != nil {
log.Printf("remove: %v", err)
}
}Expected Output
🌳 Dependency tree:
monolog/monolog 3.5.0
├── psr/log 3.0.0
└── ...
⏳ Outdated packages: 2
- monolog/monolog: 3.5.0 → 3.7.1 (semver-safe-update)
- psr/log: 3.0.0 → 3.0.2 (semver-safe-update)Advanced: WithOptions & Multi-Package Operations
Use *WithOptions variants when passing extra CLI parameters:
// Install with prefer-dist and skip scripts
comp.InstallWithOptions(map[string]string{
"prefer-dist": "",
"no-scripts": "",
})
// Require multiple packages at once
comp.RequireMultiple(map[string]string{
"monolog/monolog": "^3.0",
"symfony/console": "^7.0",
}, false)Dry Run Before Real Changes
InstallDryRun(), UpdateDryRun(packages), RequireDryRun(name, version) only print without writing to disk, suitable for CI rehearsal.
Working Directory Matters
Forgetting SetWorkingDir will execute composer in the current Go process directory, likely reporting "composer.json not found". Always point to the real PHP project root directory.
Next Steps
- Want to aggregate outdated/license/funding info into a table? See 📈 Dependency Dashboard.
- Want to auto-install Composer in CI? See 🤖 Auto-Detect & Install in CI/CD.