Skip to content

🚀 cli_basic_usage — Composer CLI Basic Usage

This example demonstrates how to create a Composer instance and execute basic Composer commands via Run / RunWithContext, while setting the working directory and environment variables and triggering a Composer self-update.

🎯 Example Positioning

cli_basic_usage is the first example (and the 8th overall) in the Composer CLI Local Operations series, and the starting point for the entire cli_* series. It breaks "getting a runnable Composer client" down into the smallest runnable unit.

  • 📚 What you'll learn: how to use composer.DefaultOptions() + composer.New() for client initialization, how to use Run to execute arbitrary commands, how to use RunWithContext for timeout control, how to adjust the execution environment via SetWorkingDir / SetEnv, and finally how to call SelfUpdate to upgrade Composer itself.
  • 🔗 Corresponding SDK methods: composer.New, Composer.Run, Composer.RunWithContext, Composer.SetWorkingDir, Composer.SetEnv, Composer.SelfUpdate.
  • 💡 Difference from basic_setup: basic_setup targets the Packagist remote API client; this example targets the local Composer CLI subprocess. They belong to different subsystems, and this example is the prerequisite for all subsequent CLI examples like cli_package_management and cli_project_management.

💻 Full Code

go
package cli_basic_usage

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/scagogogo/composer-skills/pkg/composer"
)

// Example02RunCommands demonstrates how to run basic Composer commands
func Example02RunCommands() {
	// Create a Composer instance
	options := composer.DefaultOptions()
	c, err := composer.New(options)
	if err != nil {
		log.Fatalf("Failed to create Composer instance: %v", err)
	}

	// Example 1: Use the Run method to execute a simple command
	output, err := c.Run("--version")
	if err != nil {
		log.Fatalf("Failed to execute command: %v", err)
	}
	fmt.Printf("Run method output: %s\n", output)
	// Example output: Run method output: Composer version 2.5.7 2023-12-01 11:43:14

	// Example 2: Execute a command with a timeout-bearing context
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	output, err = c.RunWithContext(ctx, "diagnose")
	if err != nil {
		log.Printf("Failed to execute command with context: %v", err)
	} else {
		fmt.Println("RunWithContext executed successfully, output omitted...")
	}

	// Example 3: Set the working directory before executing a command
	c.SetWorkingDir("/path/to/your/project")
	fmt.Printf("Working directory set to: %s\n", "/path/to/your/project")

	// Example 4: Set environment variables before executing a command
	c.SetEnv([]string{"COMPOSER_MEMORY_LIMIT=2G", "COMPOSER_NO_INTERACTION=1"})
	fmt.Println("Environment variables set: COMPOSER_MEMORY_LIMIT=2G, COMPOSER_NO_INTERACTION=1")

	// Example 5: Run the self-update command
	fmt.Println("Running self-update command...")
	err = c.SelfUpdate()
	if err != nil {
		log.Printf("Failed to update Composer: %v", err)
	} else {
		fmt.Println("Composer self-update succeeded")
	}
}

🧩 Code Walkthrough

  • 🏗️ Initialize the client: composer.DefaultOptions() provides a set of out-of-the-box default configs, which composer.New(options) then uses to construct the *Composer instance. If Composer is not detected locally, the SDK triggers an auto-install, so err must be checked.
  • Minimal execution: c.Run("--version") passes the argument straight through to the Composer subprocess and returns the merged stdout as a string. This is the lightest entry point for running any "side-effect-free query command."
  • ⏱️ Execution with timeout: context.WithTimeout(..., 30*time.Second) derives a cancellable ctx; c.RunWithContext(ctx, "diagnose") kills the subprocess on timeout or cancellation, preventing time-consuming commands like diagnose from hanging the caller. defer cancel() releases the context's resources.
  • 📁 Switch the working directory: c.SetWorkingDir("/path/to/your/project") makes all subsequent commands execute in that PHP project's root directory (equivalent to composer --working-dir), a key switch when operating across multiple projects.
  • 🌍 Inject environment variables: c.SetEnv([]string{...}) sets COMPOSER_MEMORY_LIMIT=2G (relaxing the memory limit) and COMPOSER_NO_INTERACTION=1 (disabling interactive prompts), ensuring commands run silently and stably in CI / daemons.
  • 🔄 Composer self-update: c.SelfUpdate() wraps composer self-update to upgrade the local Composer to the latest stable version. It may fail due to permissions or network, so it uses log.Printf to record rather than log.Fatalf to abort.
  • 🛡️ Error handling strategy: query-type commands (--version) use Fatalf for fast exposure; side-effecting commands (diagnose, self-update) use log.Printf for fault tolerance and continuation — reflecting the "initialization is fatal, runtime is recoverable" layering principle.

▶️ How to Run

This example is written as an Example function and must be run by explicitly invoking the corresponding file from the example directory:

bash
cd /home/cc11001100/github/scagogogo/composer-skills/examples/cli_basic_usage
go run 02_run_commands.go

⚠️ CLI examples require PHP and Composer installed locally; if not installed, the SDK will attempt an auto-install. SelfUpdate will actually upgrade the local Composer, so run it in a test environment. Before running, replace the SetWorkingDir path with a real PHP project directory on your machine.

📚 SDK Methods Involved

Method NamePackagePurposeDoc Link
Newpkg/composerConstruct a *Composer client instance from options/sdk/composer/methods/quick-setup
Runpkg/composerPass-through args to execute a Composer command, returns output string/sdk/composer/methods/run
RunWithContextpkg/composerSame as Run, but accepts context.Context for timeout/cancellation/sdk/composer/methods/run-with-context
SetWorkingDirpkg/composerSet the working directory for subsequent commands/sdk/composer/methods/set-working-dir
SetEnvpkg/composerSet subprocess environment variables/sdk/composer/environment
SelfUpdatepkg/composerRun composer self-update to upgrade Composer/sdk/composer/methods/self-update

📝 Note: SetEnv influences the subprocess by batch-injecting environment variables. Alongside the per-variable SetEnvVariable, it belongs to the environment configuration system, so the link points to the environment overview page for cross-referencing.

🚀 Going Further

  • ⏱️ Unified timeouts: extract the RunWithContext timeout into a constant and tier it by command (query 5s, install 300s); pair it with a run-with-timeout-style convenience wrapper to avoid handwriting context.WithTimeout every time.
  • 📋 Structured output: Run returns a raw string; when you need structured data like version info or dependency trees, switch to typed methods like GetVersionInfo or ShowDependencyTree to skip regex parsing.
  • 🧪 Testability: inject *Composer into business code via an interface, making it easy to stub Run in unit tests without actually spawning a subprocess.
  • 🔁 Failure retry: wrap commands prone to network fluctuation like SelfUpdate and diagnose with a layer of exponential-backoff retry, and report to monitoring after retries are exhausted.
  • 🗂️ Multi-project orchestration: when switching between projects with SetWorkingDir, pair it with c.SetEnv to inject a project-level COMPOSER config file path (COMPOSER=composer.prod.json), driving multiple deployment manifests with one client.
  • 🔐 Permissions and isolation: before running SelfUpdate in production, first call GetVersionInfo to compare the current and target versions; if necessary, lock to a specific version instead of blindly upgrading to latest, to avoid breaking changes.

Released under the MIT License