Skip to content

▶️ 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/bin path.
  • 🐘 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

MethodSignatureDescription
▶️ Execfunc (c *Composer) Exec(binary string, args ...string) (string, error)Execute vendor binary (exec binary -- args)
📋 ExecWithListfunc (c *Composer) ExecWithList() ([]string, error)List all available vendor binaries
🐘 ExecPHPfunc (c *Composer) ExecPHP(php string, binary string, args ...string) (string, error)Execute with specified PHP interpreter
📂 ExecWithWorkingDirfunc (c *Composer) ExecWithWorkingDir(binary string, workingDir string, args ...string) (string, error)Execute in specified working directory
🚀 ExecAllfunc (c *Composer) ExecAll(args ...string) (map[string]string, error)Batch execute all vendor binaries
💻 ExecCommandfunc (c *Composer) ExecCommand(command string, args ...string) (string, error)Generic command execution (composer command args)

Parameters

Exec / ExecPHP

ParameterTypeDescription
binarystringVendor binary name, e.g., phpunit
args...stringArguments passed through to the binary

ExecPHP

ParameterTypeDescription
phpstringPHP executable path, e.g., /usr/bin/php8.2

ExecWithWorkingDir

ParameterTypeDescription
workingDirstringWorking directory to temporarily switch to (auto-restored after execution)

Examples

Run 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("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

go
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

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

go
bins, err := comp.ExecWithList()
if err != nil {
	log.Fatal(err)
}
for _, b := range bins {
	fmt.Println("🧰", b)
}

Batch Execute All Binaries

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

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

Released under the MIT License