Skip to content

📦 cli_package_management — Package Management

This example demonstrates how to use the Composer Skills SDK to perform full-lifecycle dependency management in a local PHP project: install, update, add, remove, and search.

Example Positioning

cli_package_management is the "dependency management" core chapter of the Composer CLI example series. It maps everyday composer install / update / require / remove / search to Go SDK calls, letting you manipulate composer.json and the vendor/ directory from Go code without manually拼命令行 or shelling out.

After working through this example, you'll master:

  • 📥 Three postures of dependency installation (with dev, without dev, optimized autoload)
  • 🔄 Full update vs. targeted update by package name
  • ➕ Adding a normal dependency, a specified-version dependency, and a dev dependency
  • ➖ Removing a normal dependency and a dev dependency
  • 🔍 Keyword-based package search with raw-output parsing
  • 🛠️ Dump and optimization of the autoload config (dump-autoload)

The corresponding SDK methods all live in the pkg/composer package, concentrated in the dependencies.go and packages.go files.

Full Code

Below is a summary of the key logic from the three example files in the directory (duplicate log output has been trimmed; the full call chain is preserved).

01_install_update.go — Install and Update

go
package cli_package_management

import (
	"fmt"
	"log"

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

func Example01InstallUpdate() {
	c, err := composer.New(composer.DefaultOptions())
	if err != nil {
		log.Fatalf("Failed to create Composer instance: %v", err)
	}

	// Set the working directory (ensure a composer.json exists under it)
	projectDir := "/path/to/project" // Modify to your real path
	c.SetWorkingDir(projectDir)

	// 1. Install dependencies (with dev)
	err = c.Install(false, false)

	// 2. Install dependencies (without dev) — first param noDev=true
	err = c.Install(true, false)

	// 3. Install dependencies and optimize the autoloader — second param optimize=true
	err = c.Install(false, true)

	// 4. Update all dependencies (pass an empty slice)
	err = c.Update([]string{}, false)

	// 5. Update specific dependencies
	packagesToUpdate := []string{"monolog/monolog", "symfony/console"}
	err = c.Update(packagesToUpdate, false)

	// 6. Update specific dependencies (without dev)
	err = c.Update(packagesToUpdate, true)

	// 7. Only update the autoload config
	err = c.DumpAutoload(false)

	// 8. Update and optimize the autoload config
	err = c.DumpAutoload(true)

	_ = err // In real use, handle each error separately as shown above
}

02_require_remove.go — Add and Remove

go
package cli_package_management

import (
	"fmt"
	"log"

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

func Example02RequireRemove() {
	c, err := composer.New(composer.DefaultOptions())
	if err != nil {
		log.Fatalf("Failed to create Composer instance: %v", err)
	}
	c.SetWorkingDir("/path/to/project")

	// 1. Add a normal dependency (no version specified)
	err = c.RequirePackage("monolog/monolog", "", false)

	// 2. Add a dependency with a specified version
	err = c.RequirePackage("symfony/console", "^5.4", false)

	// 3. Add a dev dependency — dev=true
	err = c.RequirePackage("phpunit/phpunit", "^9.5", true)

	// 4. Remove a normal dependency
	err = c.Remove("monolog/monolog", false)

	// 5. Remove a dev dependency — dev=true
	err = c.Remove("phpunit/phpunit", true)

	// 6. Add a dependency with advanced options (options is a map[string]string)
	options := map[string]string{
		"--no-update":        "",
		"--no-progress":      "",
		"--ignore-platform-reqs": "",
	}
	err = c.RequirePackageWithOptions("guzzlehttp/guzzle", "^7.0", options)
	fmt.Println("Added dependency guzzlehttp/guzzle (version ^7.0) with advanced options")
}

04_search_package.go — Search Packages

go
package cli_package_management

import (
	"fmt"
	"log"
	"strings"

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

func Example04SearchPackage() {
	comp, err := composer.New(composer.DefaultOptions())
	if err != nil {
		log.Fatalf("Failed to initialize Composer: %v", err)
	}

	// Keyword search; returns composer's raw text output
	output, err := comp.Search("logger")
	if err != nil {
		log.Fatalf("Failed to search packages: %v", err)
	}
	fmt.Println(output)

	// Parse the output, extracting the package name from each line (first field of non-indented lines containing "/")
	for _, line := range strings.Split(output, "\n") {
		if strings.Contains(line, "/") && !strings.HasPrefix(line, " ") {
			parts := strings.Fields(line)
			if len(parts) > 0 {
				fmt.Printf("- %s\n", parts[0])
			}
		}
	}

	// A more specific search
	specificOutput, err := comp.Search("monolog")
	fmt.Println(specificOutput)
}

Code Walkthrough

Creating the instance and locating the project 🏠

All three files start with composer.New(composer.DefaultOptions()), then call c.SetWorkingDir(projectDir) to lock the command's execution directory to the target PHP project. All of Composer's dependency commands rely on composer.json, so the working directory must be correct.

Install's two boolean switches 🚦

c.Install(noDev, optimize) covers the most common install combinations with two boolean params:

  • noDev=false, optimize=false — standard install, including require-dev
  • noDev=true, optimize=false — production install, skipping dev dependencies
  • noDev=false, optimize=true — deploy-time install with optimized PSR-4/PSR-0 autoload mapping

Update's package-name slice 🔄

In c.Update(packages, noDev), passing []string{} means update all dependencies; passing a specific package-name list updates only those packages. noDev means the same as in Install. This is the common "refresh only a few libraries" approach in CI.

DumpAutoload — lightweight mapping rebuild 🗂️

c.DumpAutoload(optimize) corresponds to composer dump-autoload. It doesn't touch vendor/ contents — it only regenerates vendor/autoload.php and the mapping tables, and is very fast; optimize=true merges the PSR-4 namespace mapping to speed up production autoloading.

RequirePackage's version and dev switches ➕

c.RequirePackage(name, version, dev) has three params:

  • version of "" lets Composer auto-resolve the best version
  • dev=true writes into the require-dev section, suitable for tools like PHPUnit and PHPStan

It actually modifies composer.json and triggers dependency resolution and installation.

Remove's dev symmetry ➖

c.Remove(name, dev) is strictly symmetric with RequirePackage: dev must match the section used when the package was added, otherwise the entry won't be found in the wrong section.

RequirePackageWithOptions — fine-grained control 🎛️

When boolean switches aren't enough, c.RequirePackageWithOptions(name, version, options) accepts a map[string]string and can pass any native Composer args like --no-update, --no-progress, and --ignore-platform-reqs. Step 6 in the example demonstrates the typical "only modify composer.json, don't install immediately" usage (--no-update).

Search and output parsing 🔍

c.Search(query) returns Composer's raw text output, one package per line (vendor/name description). The example does lightweight parsing with strings.Split + strings.Fields, extracting the first field as the package name. When you need more structured results, switch to SearchOnlyName, SearchWithType, or SearchInfo which returns a structured *SearchResult.

How to Run

Requires a local environment

This series of examples executes the local composer binary and requires PHP 7.4+ and Composer 2.0+ installed locally. If not installed, the SDK's auto-install capability kicks in as a fallback. The projectDir in the example must be changed to a real PHP project path on your machine.

bash
# Clone the repository
git clone https://github.com/scagogogo/composer-skills.git
cd composer-skills

# Run the install/update example (ensure /path/to/project has been changed to a real project)
go run examples/cli_package_management/01_install_update.go

# Run the add/remove example
go run examples/cli_package_management/02_require_remove.go

# Run the search example (search itself doesn't modify the project — safest)
go run examples/cli_package_management/04_search_package.go

Recommend using a throwaway test project

require / remove actually rewrite composer.json and vendor/, so rehearse first in an empty project created with composer init to avoid polluting a production codebase.

SDK Methods Involved

Method NamePackageDoc Link
Newpkg/composer/sdk/composer/methods/new
SetWorkingDirpkg/composer/sdk/composer/methods/set-working-dir
Installpkg/composer/sdk/composer/methods/install
Updatepkg/composer/sdk/composer/methods/update
DumpAutoloadpkg/composer/sdk/composer/methods/dump-autoload
RequirePackagepkg/composer/sdk/composer/methods/require-package
Removepkg/composer/sdk/composer/methods/remove
RequirePackageWithOptionspkg/composer/sdk/composer/methods/require-package-with-options
Searchpkg/composer/sdk/composer/methods/search

Going Further

  • 🧪 Dry-run rehearsal: before the real install/update, use InstallDryRun, UpdateDryRun, RequireDryRun, and RemoveDryRun to preview upcoming changes without touching disk — suitable for dependency-change review in CI.
  • 📦 Batch operations: looping single-package RequirePackage/Remove is inefficient; switch to RequireMultiple and RemoveMultiple to handle a whole group of dependencies at once. For compliance scenarios, there are also fault-tolerant BatchRequire / BatchRemove.
  • 🌍 Global dimension: when operating on the global Composer environment, prefix this example's method names with Global — e.g. GlobalRequire and GlobalRemove; see the cli_global example.
  • 🔎 Structured search: Search returns plain text and needs manual parsing; for a package-name list, use SearchOnlyName; to filter by type, use SearchWithType; for full metadata, use SearchInfo which returns *SearchResult.
  • ⚙️ Platform and lock file: CI deploys often need the --no-dev + optimized-autoload combo; beyond the boolean params, you can also use InstallWithOptions to pass --classmap-authoritative, or InstallWithAPcu and other switches for finer deploy tuning.

Released under the MIT License