🔍 Detector
Locate the installed Composer executable across operating systems — the first step in the "detect then invoke" workflow.
Package path: github.com/scagogogo/composer-skills/pkg/detector
Composer is a PHP executable that may exist in two forms: composer (executable binary) or composer.phar (PHP archive), located in various places on the system: PATH, /usr/local/bin, Homebrew directories, COMPOSER_HOME, Windows %APPDATA%\Composer, etc. The detector package consolidates "where to find it" into a cross-platform Detector type that probes in a fixed priority order, returning on first hit.
Core Capabilities
Cross-OS Detection
Same API behaves consistently on darwin / linux / windows; platform differences are handled via build tags.
Fixed Priority
COMPOSER_PATH → COMPOSER_HOME → Common paths → which/where → composer.phar.
Detailed Result
DetectVerbose returns path + hit method + .phar flag for diagnostics.
Lightweight Check
IsInstalled checks if Composer is installed with one line, without worrying about paths.
Types and Functions
Detector
The main detector, internally holding only one state: "candidate path list".
type Detector struct {
possiblePaths []string
}NewDetector() calls defaultPossiblePaths() on creation, injecting platform-specific common paths based on the current OS (see "Platform Differences" below).
DetectionResult
Detailed result returned by DetectVerbose, telling you "how Composer was found".
type DetectionResult struct {
Path string // Detected Composer executable path
Method string // Hit method: env:COMPOSER_PATH / env:COMPOSER_HOME/vendor/bin / default_path / which/where
IsPhar bool // Whether it's a .phar file
}Detection Order
Detect and DetectVerbose probe in the following order, returning on first hit (short-circuit):
| Order | Probe Source | Method Value | Description |
|---|---|---|---|
| 1️⃣ | Environment variable COMPOSER_PATH | env:COMPOSER_PATH | User-specified binary path, highest priority |
| 2️⃣ | Environment variable COMPOSER_HOME | env:COMPOSER_HOME/vendor/bin | Concatenate $COMPOSER_HOME/vendor/bin/composer |
| 3️⃣ | possiblePaths candidate paths | default_path | Platform common paths + ./composer + ./composer.phar |
| 4️⃣ | which / where command | which/where | Let system PATH resolve composer itself |
| 5️⃣ | composer.phar fallback | — | Only Detect: composer.phar / ./composer.phar / ~/composer.phar (non-Windows) |
Why Separate Detect and DetectVerbose
Detect only returns a path string, suitable for directly passing to composer.New()'s Options.ExecutablePath; DetectVerbose additionally returns the hit method and .phar flag, suitable for diagnostic reports, logging, or deciding whether to use php composer.phar or call composer directly.
Method Signatures
NewDetector
Create a detector with candidate paths pre-filled for the current OS.
func NewDetector() *DetectorSetPossiblePaths
Replace the entire candidate path list (override default platform paths).
func (d *Detector) SetPossiblePaths(paths []string)AddPossiblePath
Append a path to existing candidates (does not override).
func (d *Detector) AddPossiblePath(path string)Detect
Detect and return the Composer executable path.
func (d *Detector) Detect() (string, error)Returns ErrExecutableNotFound (Composer executable not found) when not found.
DetectVerbose
Detect and return detailed result (path + hit method + phar flag).
func (d *Detector) DetectVerbose() (*DetectionResult, error)DetectVerbose Does Not Include phar Fallback
DetectVerbose does not include step 5's composer.phar fallback logic from Detect, covering only the first four steps. Use Detect for scenarios needing to handle scattered phar files.
IsInstalled
func (d *Detector) IsInstalled() boolEquivalent to _, err := d.Detect(); return err == nil.
Platform Differences
getPlatformSpecificPaths() selects implementation at compile time via build tags:
🍎 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 etc., !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",
}
}All three platform implementations overlay two current directory paths ./composer and ./composer.phar (uniformly appended by defaultPossiblePaths).
Executability Check
isExecutable checks the execute bit via info.Mode().Perm() & 0111 on Unix; on Windows it cannot directly determine the execute bit, treating any regular file as executable. .phar files use fileExists for existence check only.
Quick Examples
Basic Usage: Get Path and Use Directly
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 not detected: %v", err)
}
fmt.Println("Composer path:", path)
}Detailed Diagnosis: See Which Level Hit
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 not detected: %v", err)
}
fmt.Printf("Path: %s\nHit method: %s\nIs phar: %v\n",
result.Path, result.Method, result.IsPhar)
}Custom Candidate Paths
package main
import (
"fmt"
"github.com/scagogogo/composer-skills/pkg/detector"
)
func main() {
d := detector.NewDetector()
// Replace entirely to probe only internal company paths
d.SetPossiblePaths([]string{
"/opt/company/bin/composer",
"/usr/local/bin/composer",
})
// Or append one path on top of defaults
d.AddPossiblePath("/data/composer/composer.phar")
if d.IsInstalled() {
path, _ := d.Detect()
fmt.Println("Found:", path)
}
}Advanced
- Working with Installer: When
DetectreturnsErrExecutableNotFound, you can callpkg/installerto auto-install viaEnsureComposerInstalled. - Working with Composer: After getting the path, pass it to
composer.Options.ExecutablePathto avoid anotherLookPathinsidepkg/composer. - COMPOSER_PATH semantics: It only affects the detector, not a native Composer environment variable; very convenient for locking "which composer to use", especially on machines with multiple versions coexisting (Composer 1 / 2 / preview).
- Concurrency safety:
Detectorhas no locking,SetPossiblePaths/AddPossiblePathmodify internal slices; synchronize externally when sharing one instance across goroutines, or let each goroutineNewDetector()separately.