▶️ Execute Vendor Binaries
Execute tools installed by Composer into vendor/bin (PHPUnit, PHPStan, Psalm, etc.), and provide list, batch execution, specified PHP interpreter, and working directory capabilities.
composer exec automatically adds vendor/bin to PATH, saving you from writing long paths like ./vendor/bin/phpunit. Composer Skills adds ExecAll (batch execute all binaries) and ExecCommand (generic command execution) on top.
When to Use
- 🧪 Call vendor tools like PHPUnit / PHPStan in Go-orchestrated toolchains, without caring about
vendor/binpath. - 🐘 Need to run a tool with a specific PHP version (multi-version test matrix).
- 📂 Execute tools in a subproject different from the default working directory.
- 🚀 Run all vendor binaries at once (e.g., batch self-check scripts).
Method Signatures
| Method | Signature | Description |
|---|---|---|
| ▶️ Exec | func (c *Composer) Exec(binary string, args ...string) (string, error) | Execute vendor binary (exec binary -- args) |
| 📋 ExecWithList | func (c *Composer) ExecWithList() ([]string, error) | List all available vendor binaries |
| 🐘 ExecPHP | func (c *Composer) ExecPHP(php string, binary string, args ...string) (string, error) | Execute with specified PHP interpreter |
| 📂 ExecWithWorkingDir | func (c *Composer) ExecWithWorkingDir(binary string, workingDir string, args ...string) (string, error) | Execute in specified working directory |
| 🚀 ExecAll | func (c *Composer) ExecAll(args ...string) (map[string]string, error) | Batch execute all vendor binaries |
| 💻 ExecCommand | func (c *Composer) ExecCommand(command string, args ...string) (string, error) | Generic command execution (composer command args) |
Parameters
Exec / ExecPHP
| Parameter | Type | Description |
|---|---|---|
binary | string | Vendor binary name, e.g., phpunit |
args | ...string | Arguments passed through to the binary |
ExecPHP
| Parameter | Type | Description |
|---|---|---|
php | string | PHP executable path, e.g., /usr/bin/php8.2 |
ExecWithWorkingDir
| Parameter | Type | Description |
|---|---|---|
workingDir | string | Working directory to temporarily switch to (auto-restored after execution) |
Examples
Run PHPUnit
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("Initialization failed: %v", err)
}
out, err := comp.Exec("phpunit", "--testsuite=unit", "--stop-on-failure")
if err != nil {
log.Fatalf("PHPUnit failed: %v\n%s", err, out)
}
fmt.Println(out)
}Run PHPStan with Specific PHP Version
out, err := comp.ExecPHP("/usr/bin/php8.3", "phpstan", "analyse", "src", "--level=8")
if err != nil {
log.Fatalf("PHPStan failed: %v\n%s", err, out)
}
fmt.Println(out)Execute in Subdirectory
// workingDir is automatically restored to original value after function returns
out, err := comp.ExecWithWorkingDir("phpunit", "/path/to/subproject", "--testdox")
if err != nil {
log.Fatal(err)
}
fmt.Println(out)List Available Binaries
bins, err := comp.ExecWithList()
if err != nil {
log.Fatal(err)
}
for _, b := range bins {
fmt.Println("🧰", b)
}Batch Execute All Binaries
// Pass --version through to each vendor binary
results, err := comp.ExecAll("--version")
if err != nil {
log.Fatal(err)
}
for bin, out := range results {
fmt.Printf("=== %s ===\n%s\n", bin, out)
}Generic Command Execution
// Directly execute a composer subcommand
out, err := comp.ExecCommand("show", "--self")
if err != nil {
log.Fatal(err)
}
fmt.Println(out)Advanced
Exec Argument Separation
Exec inserts -- between binary and args to ensure subsequent arguments aren't parsed by composer exec itself. For example, Exec("phpunit", "--filter=testFoo") actually executes composer exec phpunit -- --filter=testFoo.
ExecAll Error Isolation
ExecAll internally calls Exec for each binary individually; a single failure doesn't interrupt the overall flow — failed tool output is replaced with "Error: <err>" in the result map. The returned error is non-nil only when ExecWithList fails.
ExecWithWorkingDir Not Thread-safe
This method is implemented by temporarily modifying c.workingDir and defer-restoring it. Don't call it when multiple goroutines share the same *Composer instance, or working directories will overwrite each other.