📄 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: useReadComposerJSONto get a template,SetPropertyto fill fields, thenWriteComposerJSON. - ➕ Add dependencies and scripts to existing projects without triggering the
composer requireinstallation flow (just want to modify the file). - 🔧 Batch modify
configin CI (process-timeout,vendor-dir, etc.). - 🧪 Inject fake data in unit tests with
SetMockComposerJSONto avoid depending on real files.
Structured Types
ComposerJSON
Complete structure in composer_json.go, corresponding to all standard fields of composer.json.
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.
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)
| Method | Signature | Description |
|---|---|---|
| 📖 ReadComposerJSON | func (c *Composer) ReadComposerJSON() (*ComposerJSON, error) | Read and parse composer.json |
| 💾 WriteComposerJSON | func (c *Composer) WriteComposerJSON(composerJSON *ComposerJSON) error | Write back composer.json |
| ➕ AddRequire | func (c *Composer) AddRequire(packageName, version string, isDev bool) error | Append dependency to require / require-dev |
| ➖ RemoveRequire | func (c *Composer) RemoveRequire(packageName string, isDev bool) error | Remove dependency |
| 📜 AddScript | func (c *Composer) AddScript(name string, script interface{}, description string) error | Add script with optional description |
| 🗑️ RemoveScript | func (c *Composer) RemoveScript(name string) error | Remove script and description |
| 🧩 AddAutoload | func (c *Composer) AddAutoload(type_ string, namespace string, paths interface{}, isDev bool) error | Add autoload rule |
| ⚙️ SetConfig | func (c *Composer) SetConfig(key string, value interface{}) error | Set config field |
| 🔍 GetConfig | func (c *Composer) GetConfig(key string) (interface{}, error) | Read config field |
| 🏷️ SetProperty | func (c *Composer) SetProperty(property string, value interface{}) error | Set top-level property |
convenience.go (Pure Query)
| Method | Signature | Description |
|---|---|---|
| 📖 ReadComposerJson | func (c *Composer) ReadComposerJson() (*ComposerJsonData, error) | Read as lightweight structure |
| 📂 ReadComposerJsonFile | func ReadComposerJsonFile(filePath string) (*ComposerJsonData, error) | Read by path (package-level function) |
| 🔒 ReadComposerLock | func (c *Composer) ReadComposerLock() (*ComposerLockData, error) | Read composer.lock |
| 🔒 ReadComposerLockFile | func ReadComposerLockFile(filePath string) (*ComposerLockData, error) | Read lock by path |
Parameters
AddRequire / RemoveRequire
| Parameter | Type | Description |
|---|---|---|
packageName | string | Package name, e.g., symfony/console |
version | string | Version constraint, e.g., ^5.0 |
isDev | bool | true writes to require-dev, otherwise require |
AddScript
| Parameter | Type | Description |
|---|---|---|
name | string | Script name, e.g., post-install-cmd |
script | interface{} | String command, array of strings, or PHP class call |
description | string | Description (empty string means no scripts-descriptions written) |
AddAutoload
| Parameter | Type | Description |
|---|---|---|
type_ | string | psr-4 / psr-0 / classmap / files |
namespace | string | Namespace, e.g., App\ |
paths | interface{} | Path string or array of strings |
isDev | bool | true writes to autoload-dev |
SetProperty
| Parameter | Type | Description |
|---|---|---|
property | string | Only supports name/description/type/keywords/homepage/license/minimum-stability/prefer-stable |
value | interface{} | Value type matching the property |
Examples
Programmatically Initialize a New Project
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
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
// 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)
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.