Skip to content

🔍 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".

go
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".

go
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):

OrderProbe SourceMethod ValueDescription
1️⃣Environment variable COMPOSER_PATHenv:COMPOSER_PATHUser-specified binary path, highest priority
2️⃣Environment variable COMPOSER_HOMEenv:COMPOSER_HOME/vendor/binConcatenate $COMPOSER_HOME/vendor/bin/composer
3️⃣possiblePaths candidate pathsdefault_pathPlatform common paths + ./composer + ./composer.phar
4️⃣which / where commandwhich/whereLet system PATH resolve composer itself
5️⃣composer.phar fallbackOnly 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.

go
func NewDetector() *Detector

SetPossiblePaths

Replace the entire candidate path list (override default platform paths).

go
func (d *Detector) SetPossiblePaths(paths []string)

AddPossiblePath

Append a path to existing candidates (does not override).

go
func (d *Detector) AddPossiblePath(path string)

Detect

Detect and return the Composer executable path.

go
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).

go
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

go
func (d *Detector) IsInstalled() bool

Equivalent to _, err := d.Detect(); return err == nil.

Platform Differences

getPlatformSpecificPaths() selects implementation at compile time via build tags:

🍎 Darwin (macOS)

go
//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
//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
//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

go
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

go
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

go
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 Detect returns ErrExecutableNotFound, you can call pkg/installer to auto-install via EnsureComposerInstalled.
  • Working with Composer: After getting the path, pass it to composer.Options.ExecutablePath to avoid another LookPath inside pkg/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: Detector has no locking, SetPossiblePaths / AddPossiblePath modify internal slices; synchronize externally when sharing one instance across goroutines, or let each goroutine NewDetector() separately.

Released under the MIT License