🔍 检测器 Detector
跨操作系统定位系统中已安装的 Composer 可执行文件,是「先探测、再调用」工作流的第一步。
包路径:github.com/scagogogo/composer-skills/pkg/detector
Composer 是一个 PHP 可执行文件,可能以 composer(可执行二进制)或 composer.phar(PHP 归档)两种形态存在于系统的不同位置:PATH、/usr/local/bin、Homebrew 目录、COMPOSER_HOME、Windows 的 %APPDATA%\Composer 等。detector 包把「在哪儿能找到它」这件事收敛成一个跨平台的 Detector 类型,按固定优先级逐个探测,命中即返回。
核心能力清单
跨 OS 探测
同一份 API 在 darwin / linux / windows 上行为一致,平台差异通过 build tag 自动切换。
固定优先级
COMPOSER_PATH → COMPOSER_HOME → 常见路径 → which/where → composer.phar。
详细结果
DetectVerbose 返回路径 + 命中方式 + 是否 .phar,便于诊断。
轻量判断
IsInstalled 一行代码判断是否已装,无需关心路径。
类型与函数
Detector
检测器主体,内部只持有「候选路径列表」一个状态。
type Detector struct {
possiblePaths []string
}NewDetector() 创建时会调用 defaultPossiblePaths(),根据当前操作系统注入平台特定的常见路径(详见下文「平台差异」)。
DetectionResult
DetectVerbose 返回的详细结果,告诉你 Composer 是「怎么被找到的」。
type DetectionResult struct {
Path string // 检测到的 Composer 可执行文件路径
Method string // 命中方式:env:COMPOSER_PATH / env:COMPOSER_HOME/vendor/bin / default_path / which/where
IsPhar bool // 是否为 .phar 文件
}检测顺序
Detect 与 DetectVerbose 按以下顺序逐级探测,命中即返回(短路):
| 顺序 | 探测源 | Method 值 | 说明 |
|---|---|---|---|
| 1️⃣ | 环境变量 COMPOSER_PATH | env:COMPOSER_PATH | 用户显式指定的二进制路径,最高优先 |
| 2️⃣ | 环境变量 COMPOSER_HOME | env:COMPOSER_HOME/vendor/bin | 拼接 $COMPOSER_HOME/vendor/bin/composer |
| 3️⃣ | possiblePaths 候选路径 | default_path | 平台常见路径 + ./composer + ./composer.phar |
| 4️⃣ | which / where 命令 | which/where | 让系统 PATH 自己解析 composer |
| 5️⃣ | composer.phar 兜底 | — | 仅 Detect:composer.phar / ./composer.phar / ~/composer.phar(非 Windows) |
为什么要分 Detect 和 DetectVerbose
Detect 只返回路径字符串,适合直接喂给 composer.New() 的 Options.ExecutablePath;DetectVerbose 额外返回命中方式与 .phar 标记,适合做诊断报告、日志或决定后续用 php composer.phar 还是直接调用 composer。
方法签名
NewDetector
创建检测器,候选路径已按当前 OS 预填。
func NewDetector() *DetectorSetPossiblePaths
整体替换候选路径列表(覆盖默认平台路径)。
func (d *Detector) SetPossiblePaths(paths []string)AddPossiblePath
在已有候选路径后追加一条(不覆盖)。
func (d *Detector) AddPossiblePath(path string)Detect
检测并返回 Composer 可执行文件路径。
func (d *Detector) Detect() (string, error)未找到时返回 ErrExecutableNotFound(未找到composer可执行文件)。
DetectVerbose
检测并返回详细结果(路径 + 命中方式 + 是否 phar)。
func (d *Detector) DetectVerbose() (*DetectionResult, error)DetectVerbose 不含 phar 兜底
DetectVerbose 不包含 Detect 第 5 步的 composer.phar 文件兜底逻辑,只覆盖前四步。需要兼容散落 phar 的场景时请用 Detect。
IsInstalled
func (d *Detector) IsInstalled() bool等价于 _, err := d.Detect(); return err == nil。
平台差异
getPlatformSpecificPaths() 通过 build tag 在编译期选择实现:
🍎 Darwin(macOS)
//go:build darwin
func getPlatformSpecificPaths() []string {
return []string{
"/usr/local/bin/composer",
"/usr/bin/composer",
"/opt/homebrew/bin/composer", // Apple Silicon Homebrew
filepath.Join(os.Getenv("HOME"), ".composer/vendor/bin/composer"),
filepath.Join(os.Getenv("HOME"), "composer.phar"),
}
}🐧 Unix(Linux 等,!windows && !darwin)
//go:build !windows && !darwin
func getPlatformSpecificPaths() []string {
return []string{
"/usr/local/bin/composer",
"/usr/bin/composer",
filepath.Join(os.Getenv("HOME"), ".composer/vendor/bin/composer"),
filepath.Join(os.Getenv("HOME"), "composer.phar"),
}
}🪟 Windows
//go:build windows
func getPlatformSpecificPaths() []string {
return []string{
filepath.Join(os.Getenv("APPDATA"), "Composer", "composer.phar"),
filepath.Join(os.Getenv("ProgramFiles"), "Composer", "composer.phar"),
filepath.Join(os.Getenv("ProgramFiles(x86)"), "Composer", "composer.phar"),
"composer.phar",
"composer.bat",
"composer",
}
}三个平台实现都会再叠加 ./composer 与 ./composer.phar 两条当前目录通用路径(由 defaultPossiblePaths 统一追加)。
可执行性判断
isExecutable 在 Unix 系通过 info.Mode().Perm() & 0111 检查执行位;在 Windows 上无法直接判定执行位,只要是普通文件即视为可执行。.phar 文件用 fileExists 判断存在性即可。
快速示例
基础用法:拿到路径直接用
package main
import (
"fmt"
"log"
"github.com/scagogogo/composer-skills/pkg/detector"
)
func main() {
d := detector.NewDetector()
path, err := d.Detect()
if err != nil {
log.Fatalf("未检测到 Composer: %v", err)
}
fmt.Println("Composer 路径:", path)
}详细诊断:看命中了哪一级
package main
import (
"fmt"
"log"
"github.com/scagogogo/composer-skills/pkg/detector"
)
func main() {
d := detector.NewDetector()
result, err := d.DetectVerbose()
if err != nil {
log.Fatalf("未检测到 Composer: %v", err)
}
fmt.Printf("路径: %s\n命中方式: %s\n是否 phar: %v\n",
result.Path, result.Method, result.IsPhar)
}自定义候选路径
package main
import (
"fmt"
"github.com/scagogogo/composer-skills/pkg/detector"
)
func main() {
d := detector.NewDetector()
// 整体替换为只探测企业内部路径
d.SetPossiblePaths([]string{
"/opt/company/bin/composer",
"/usr/local/bin/composer",
})
// 或在默认路径基础上追加一条
d.AddPossiblePath("/data/composer/composer.phar")
if d.IsInstalled() {
path, _ := d.Detect()
fmt.Println("找到:", path)
}
}进阶
- 与 Installer 配合:
Detect返回ErrExecutableNotFound时,可转而调用pkg/installer的EnsureComposerInstalled自动安装。 - 与 Composer 配合:拿到路径后,把它传给
composer.Options.ExecutablePath,避免pkg/composer内部再次LookPath。 - COMPOSER_PATH 的语义:它仅作用于 detector,并非 Composer 原生环境变量;用来锁定「用哪一个 composer」非常方便,尤其适合多版本共存(Composer 1 / 2 / preview)的机器。
- 并发安全:
Detector未加锁,SetPossiblePaths/AddPossiblePath会修改内部切片;多 goroutine 共享同一实例时应在外部同步,或每个 goroutine 各自NewDetector()。