▶️ 执行厂商二进制文件
执行 Composer 安装到 vendor/bin 下的工具(PHPUnit、PHPStan、Psalm 等),并提供列出、批量执行、指定 PHP 解释器与工作目录等能力。
composer exec 会自动把 vendor/bin 加入 PATH,避免你手写 ./vendor/bin/phpunit 这种长路径。Composer Skills 在此基础上加了 ExecAll(批量执行所有二进制)和 ExecCommand(通用命令执行)两个便捷封装。
何时使用
- 🧪 在 Go 编排的工具链里调用 PHPUnit / PHPStan 等厂商工具,不想关心
vendor/bin路径。 - 🐘 需要用特定 PHP 版本运行某个工具(多版本测试矩阵)。
- 📂 在与默认工作目录不同的子项目里执行工具。
- 🚀 一次性运行所有厂商二进制(如批量自检脚本)。
方法签名
| 方法 | 签名 | 说明 |
|---|---|---|
| ▶️ Exec | func (c *Composer) Exec(binary string, args ...string) (string, error) | 执行厂商二进制(exec binary -- args) |
| 📋 ExecWithList | func (c *Composer) ExecWithList() ([]string, error) | 列出所有可用厂商二进制 |
| 🐘 ExecPHP | func (c *Composer) ExecPHP(php string, binary string, args ...string) (string, error) | 用指定 PHP 解释器执行 |
| 📂 ExecWithWorkingDir | func (c *Composer) ExecWithWorkingDir(binary string, workingDir string, args ...string) (string, error) | 在指定工作目录执行 |
| 🚀 ExecAll | func (c *Composer) ExecAll(args ...string) (map[string]string, error) | 对所有厂商二进制批量执行 |
| 💻 ExecCommand | func (c *Composer) ExecCommand(command string, args ...string) (string, error) | 通用命令执行(composer command args) |
参数说明
Exec / ExecPHP
| 参数 | 类型 | 说明 |
|---|---|---|
binary | string | 厂商二进制名,如 phpunit |
args | ...string | 透传给二进制的参数 |
ExecPHP
| 参数 | 类型 | 说明 |
|---|---|---|
php | string | PHP 可执行文件路径,如 /usr/bin/php8.2 |
ExecWithWorkingDir
| 参数 | 类型 | 说明 |
|---|---|---|
workingDir | string | 临时切换到的工作目录(执行后自动恢复) |
示例
运行 PHPUnit
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.Fatalf("初始化失败: %v", err)
}
out, err := comp.Exec("phpunit", "--testsuite=unit", "--stop-on-failure")
if err != nil {
log.Fatalf("PHPUnit 失败: %v\n%s", err, out)
}
fmt.Println(out)
}用指定 PHP 版本运行 PHPStan
go
out, err := comp.ExecPHP("/usr/bin/php8.3", "phpstan", "analyse", "src", "--level=8")
if err != nil {
log.Fatalf("PHPStan 失败: %v\n%s", err, out)
}
fmt.Println(out)在子目录执行
go
// workingDir 在函数返回后自动恢复原值
out, err := comp.ExecWithWorkingDir("phpunit", "/path/to/subproject", "--testdox")
if err != nil {
log.Fatal(err)
}
fmt.Println(out)列出可用二进制
go
bins, err := comp.ExecWithList()
if err != nil {
log.Fatal(err)
}
for _, b := range bins {
fmt.Println("🧰", b)
}批量执行所有二进制
go
// 把 --version 透传给每个厂商二进制
results, err := comp.ExecAll("--version")
if err != nil {
log.Fatal(err)
}
for bin, out := range results {
fmt.Printf("=== %s ===\n%s\n", bin, out)
}通用命令执行
go
// 直接执行 composer 的某个子命令
out, err := comp.ExecCommand("show", "--self")
if err != nil {
log.Fatal(err)
}
fmt.Println(out)进阶
Exec 的参数分隔
Exec 在 binary 与 args 之间插入 --,确保后续参数不被 composer exec 自己解析。例如 Exec("phpunit", "--filter=testFoo") 实际执行 composer exec phpunit -- --filter=testFoo。
ExecAll 的错误隔离
ExecAll 内部对每个二进制单独调用 Exec,单个失败不会中断整体流程——失败的工具输出会被替换为 "Error: <err>" 写入结果 map。返回的 error 仅在 ExecWithList 失败时非 nil。
ExecWithWorkingDir 非线程安全
该方法通过临时修改 c.workingDir 并 defer 恢复实现。不要在多个 goroutine 共用同一个 *Composer 实例时调用它,否则工作目录会被互相覆盖。