Skip to content

📄 composer.json Operations

Directly read and write the composer.json file, managing dependencies, scripts, autoload, config, and top-level properties — pure file operations without going through the composer require command.

Composer Skills centralizes all structured operations on composer.json in composer_json.go, providing high-level methods with the "read → modify → write" atomic pattern (AddRequire, AddScript, AddAutoload, SetConfig, SetProperty, etc.). Companion convenience methods ReadComposerJson / ReadComposerLock (in convenience.go) provide a lighter data structure for pure query scenarios.

When to Use

  • 🧱 Scaffolding tools that programmatically generate composer.json: use ReadComposerJSON to get a template, SetProperty to fill fields, then WriteComposerJSON.
  • ➕ Add dependencies and scripts to existing projects without triggering the composer require installation flow (just want to modify the file).
  • 🔧 Batch modify config in CI (process-timeout, vendor-dir, etc.).
  • 🧪 Inject fake data in unit tests with SetMockComposerJSON to avoid depending on real files.

Structured Types

ComposerJSON

Complete structure in composer_json.go, corresponding to all standard fields of composer.json.

go
type ComposerJSON struct {
	Name                string                 `json:"name,omitempty"`
	Description         string                 `json:"description,omitempty"`
	Type                string                 `json:"type,omitempty"`
	Keywords            []string               `json:"keywords,omitempty"`
	Homepage            string                 `json:"homepage,omitempty"`
	License             interface{}            `json:"license,omitempty"`
	Authors             []map[string]string    `json:"authors,omitempty"`
	Support             map[string]string      `json:"support,omitempty"`
	Require             map[string]string      `json:"require,omitempty"`
	RequireDev          map[string]string      `json:"require-dev,omitempty"`
	Suggest             map[string]string      `json:"suggest,omitempty"`
	Autoload            map[string]interface{} `json:"autoload,omitempty"`
	AutoloadDev         map[string]interface{} `json:"autoload-dev,omitempty"`
	Repositories        map[string]interface{} `json:"repositories,omitempty"`
	Config              map[string]interface{} `json:"config,omitempty"`
	Scripts             map[string]interface{} `json:"scripts,omitempty"`
	ScriptsDescriptions map[string]string      `json:"scripts-descriptions,omitempty"`
	Extra               map[string]interface{} `json:"extra,omitempty"`
	Bin                 []string               `json:"bin,omitempty"`
	Archive             map[string]interface{} `json:"archive,omitempty"`
	NonFeatureBranches  []string               `json:"non-feature-branches,omitempty"`
	MinimumStability    string                 `json:"minimum-stability,omitempty"`
	PreferStable        bool                   `json:"prefer-stable,omitempty"`
	Replace             map[string]string      `json:"replace,omitempty"`
	Conflict            map[string]string      `json:"conflict,omitempty"`
	Provide             map[string]string      `json:"provide,omitempty"`
}

License is interface{}

The License field type is interface{} because Composer allows it to be a string ("MIT") or an array of strings (["MIT", "BSD-2-Clause"]).

ComposerJsonData / ComposerLockData

Lightweight structures in convenience.go for pure file queries (no write-back). ComposerJsonData has slightly different field sets from ComposerJSON (e.g., Repositories is []interface{} instead of a map). ComposerLockData corresponds to the root of composer.lock.

go
type ComposerJsonData struct {
	Name             string                 `json:"name,omitempty"`
	Description      string                 `json:"description,omitempty"`
	Type             string                 `json:"type,omitempty"`
	Keywords         []string               `json:"keywords,omitempty"`
	Require          map[string]string      `json:"require,omitempty"`
	RequireDev       map[string]string      `json:"require-dev,omitempty"`
	Autoload         map[string]interface{} `json:"autoload,omitempty"`
	Scripts          map[string]interface{} `json:"scripts,omitempty"`
	Config           map[string]interface{} `json:"config,omitempty"`
	// ... other fields see convenience.go
}

type ComposerLockData struct {
	ContentHash      string            `json:"content-hash,omitempty"`
	Packages         []LockPackageData `json:"packages,omitempty"`
	PackagesDev      []LockPackageData `json:"packages-dev,omitempty"`
	Platform         map[string]string `json:"platform,omitempty"`
	PlatformDev      map[string]string `json:"platform-dev,omitempty"`
	PluginApiVersion string            `json:"plugin-api-version,omitempty"`
}

type LockPackageData struct {
	Name       string            `json:"name"`
	Version    string            `json:"version"`
	Type       string            `json:"type,omitempty"`
	License    []string          `json:"license,omitempty"`
	Abandoned  interface{}       `json:"abandoned,omitempty"`
	// ... other fields see convenience.go
}

Method Signatures

composer_json.go (Write-back)

MethodSignatureDescription
📖 ReadComposerJSONfunc (c *Composer) ReadComposerJSON() (*ComposerJSON, error)Read and parse composer.json
💾 WriteComposerJSONfunc (c *Composer) WriteComposerJSON(composerJSON *ComposerJSON) errorWrite back composer.json
➕ AddRequirefunc (c *Composer) AddRequire(packageName, version string, isDev bool) errorAppend dependency to require / require-dev
➖ RemoveRequirefunc (c *Composer) RemoveRequire(packageName string, isDev bool) errorRemove dependency
📜 AddScriptfunc (c *Composer) AddScript(name string, script interface{}, description string) errorAdd script with optional description
🗑️ RemoveScriptfunc (c *Composer) RemoveScript(name string) errorRemove script and description
🧩 AddAutoloadfunc (c *Composer) AddAutoload(type_ string, namespace string, paths interface{}, isDev bool) errorAdd autoload rule
⚙️ SetConfigfunc (c *Composer) SetConfig(key string, value interface{}) errorSet config field
🔍 GetConfigfunc (c *Composer) GetConfig(key string) (interface{}, error)Read config field
🏷️ SetPropertyfunc (c *Composer) SetProperty(property string, value interface{}) errorSet top-level property

convenience.go (Pure Query)

MethodSignatureDescription
📖 ReadComposerJsonfunc (c *Composer) ReadComposerJson() (*ComposerJsonData, error)Read as lightweight structure
📂 ReadComposerJsonFilefunc ReadComposerJsonFile(filePath string) (*ComposerJsonData, error)Read by path (package-level function)
🔒 ReadComposerLockfunc (c *Composer) ReadComposerLock() (*ComposerLockData, error)Read composer.lock
🔒 ReadComposerLockFilefunc ReadComposerLockFile(filePath string) (*ComposerLockData, error)Read lock by path

Parameters

AddRequire / RemoveRequire

ParameterTypeDescription
packageNamestringPackage name, e.g., symfony/console
versionstringVersion constraint, e.g., ^5.0
isDevbooltrue writes to require-dev, otherwise require

AddScript

ParameterTypeDescription
namestringScript name, e.g., post-install-cmd
scriptinterface{}String command, array of strings, or PHP class call
descriptionstringDescription (empty string means no scripts-descriptions written)

AddAutoload

ParameterTypeDescription
type_stringpsr-4 / psr-0 / classmap / files
namespacestringNamespace, e.g., App\
pathsinterface{}Path string or array of strings
isDevbooltrue writes to autoload-dev

SetProperty

ParameterTypeDescription
propertystringOnly supports name/description/type/keywords/homepage/license/minimum-stability/prefer-stable
valueinterface{}Value type matching the property

Examples

Programmatically Initialize a New Project

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.Fatal(err)
	}

	// Start from empty config
	cj := &composer.ComposerJSON{}
	cj.Name = "acme/widget"
	cj.Description = "An awesome widget library"
	cj.Type = "library"
	cj.License = "MIT"
	cj.Authors = []map[string]string{
		{"name": "Acme Team", "email": "dev@acme.io"},
	}

	// Write out initial composer.json
	if err := comp.WriteComposerJSON(cj); err != nil {
		log.Fatal(err)
	}

	// Append dependencies and scripts
	if err := comp.AddRequire("symfony/console", "^6.0", false); err != nil {
		log.Fatal(err)
	}
	if err := comp.AddRequire("phpunit/phpunit", "^10.0", true); err != nil {
		log.Fatal(err)
	}
	if err := comp.AddScript("test", "phpunit", "Run test suite"); err != nil {
		log.Fatal(err)
	}

	// PSR-4 autoload
	if err := comp.AddAutoload("psr-4", "Acme\\Widget\\", "src/", false); err != nil {
		log.Fatal(err)
	}

	fmt.Println("✅ composer.json generated")
}

Read and Modify config

go
cj, err := comp.ReadComposerJSON()
if err != nil {
	if err == composer.ErrComposerJSONNotFound {
		log.Fatal("No composer.json in current directory")
	}
	log.Fatal(err)
}
fmt.Printf("Project: %s\n", cj.Name)

// Modify process timeout
if err := comp.SetConfig("process-timeout", 600); err != nil {
	log.Fatal(err)
}

// Read back
v, _ := comp.GetConfig("process-timeout")
fmt.Printf("process-timeout = %v\n", v)

Read-only Query with Convenience Function

go
// Lightweight structure, suitable for read-only scenarios
data, err := comp.ReadComposerJson()
if err != nil {
	log.Fatal(err)
}
fmt.Printf("Direct dependencies: %d\n", len(data.Require))

lock, err := comp.ReadComposerLock()
if err != nil {
	log.Fatal(err)
}
fmt.Printf("Installed packages: %d\n", len(lock.Packages))

Read by Absolute Path (Independent of Working Directory)

go
data, err := composer.ReadComposerJsonFile("/srv/apps/myapp/composer.json")
if err != nil {
	log.Fatal(err)
}
fmt.Println(data.Name)

Advanced

Difference Between Two Read APIs

  • ReadComposerJSON() (composer_json.go) → *ComposerJSON, complete fields, write-back supported, mock supported.
  • ReadComposerJson() (convenience.go) → *ComposerJsonData, fields more focused on querying, no mock support.

Use the former when you need WriteComposerJSON; use the latter for lighter read-only statistics.

AddRequire Does Not Perform Installation

AddRequire only modifies the composer.json file and does not trigger composer install. To actually install dependencies, you need to additionally execute comp.Install(false, false) or use RequirePackage (via the composer require command) after.

SetProperty's Hardcoded Whitelist

SetProperty uses a switch to hardcode supported property names. Passing properties like bin, authors that are not listed returns an unsupported property error. To set these fields, directly manipulate *ComposerJSON and call WriteComposerJSON.

Mock for Testing

SetMockComposerJSON / ClearMockComposerJSON can make ReadComposerJSON return injected fake data, no real file needed for unit testing. Note: the convenience version ReadComposerJson is not affected by this mock.

Released under the MIT License