📄 composer.json 操作
直接读写 composer.json 文件,管理依赖、脚本、自动加载、config 与顶级属性——不经过 composer require 命令,纯文件操作。
Composer Skills 把对 composer.json 的所有结构化操作集中在 composer_json.go,并提供「读 → 改 → 写」原子模式的高层方法(AddRequire、AddScript、AddAutoload、SetConfig、SetProperty 等)。配套的便捷方法 ReadComposerJson / ReadComposerLock(位于 convenience.go)提供另一套更轻量的数据结构,便于纯查询场景使用。
何时使用
- 🧱 脚手架工具程序化生成
composer.json:先ReadComposerJSON拿模板,再SetProperty填字段,最后WriteComposerJSON。 - ➕ 给已有项目追加依赖与脚本,且不想触发
composer require的安装流程(只想改文件)。 - 🔧 CI 里批量改
config(process-timeout、vendor-dir等)。 - 🧪 单元测试用
SetMockComposerJSON注入假数据,避免依赖真实文件。
结构化类型
ComposerJSON
composer_json.go 中的完整结构,对应 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 是 interface{}
License 字段类型为 interface{},因为 Composer 允许它是字符串("MIT")或字符串数组(["MIT", "BSD-2-Clause"])。
ComposerJsonData / ComposerLockData
convenience.go 中的轻量结构,用于纯文件查询(不写回)。ComposerJsonData 字段集与 ComposerJSON 略有差异(如 Repositories 是 []interface{} 而非 map)。ComposerLockData 对应 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"`
// ... 其余字段见 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"`
// ... 其余字段见 convenience.go
}方法签名
composer_json.go(写回型)
| 方法 | 签名 | 说明 |
|---|---|---|
| 📖 ReadComposerJSON | func (c *Composer) ReadComposerJSON() (*ComposerJSON, error) | 读取并解析 composer.json |
| 💾 WriteComposerJSON | func (c *Composer) WriteComposerJSON(composerJSON *ComposerJSON) error | 写回 composer.json |
| ➕ AddRequire | func (c *Composer) AddRequire(packageName, version string, isDev bool) error | 追加依赖到 require / require-dev |
| ➖ RemoveRequire | func (c *Composer) RemoveRequire(packageName string, isDev bool) error | 移除依赖 |
| 📜 AddScript | func (c *Composer) AddScript(name string, script interface{}, description string) error | 添加脚本及可选描述 |
| 🗑️ RemoveScript | func (c *Composer) RemoveScript(name string) error | 移除脚本及描述 |
| 🧩 AddAutoload | func (c *Composer) AddAutoload(type_ string, namespace string, paths interface{}, isDev bool) error | 添加自动加载规则 |
| ⚙️ SetConfig | func (c *Composer) SetConfig(key string, value interface{}) error | 设置 config 字段 |
| 🔍 GetConfig | func (c *Composer) GetConfig(key string) (interface{}, error) | 读取 config 字段 |
| 🏷️ SetProperty | func (c *Composer) SetProperty(property string, value interface{}) error | 设置顶级属性 |
convenience.go(纯查询型)
| 方法 | 签名 | 说明 |
|---|---|---|
| 📖 ReadComposerJson | func (c *Composer) ReadComposerJson() (*ComposerJsonData, error) | 读取为轻量结构 |
| 📂 ReadComposerJsonFile | func ReadComposerJsonFile(filePath string) (*ComposerJsonData, error) | 按路径读取(包级函数) |
| 🔒 ReadComposerLock | func (c *Composer) ReadComposerLock() (*ComposerLockData, error) | 读取 composer.lock |
| 🔒 ReadComposerLockFile | func ReadComposerLockFile(filePath string) (*ComposerLockData, error) | 按路径读取 lock |
参数说明
AddRequire / RemoveRequire
| 参数 | 类型 | 说明 |
|---|---|---|
packageName | string | 包名,如 symfony/console |
version | string | 版本约束,如 ^5.0 |
isDev | bool | true 写入 require-dev,否则 require |
AddScript
| 参数 | 类型 | 说明 |
|---|---|---|
name | string | 脚本名,如 post-install-cmd |
script | interface{} | 字符串命令、字符串数组或 PHP 类调用 |
description | string | 描述(空串则不写 scripts-descriptions) |
AddAutoload
| 参数 | 类型 | 说明 |
|---|---|---|
type_ | string | psr-4 / psr-0 / classmap / files |
namespace | string | 命名空间,如 App\ |
paths | interface{} | 路径字符串或字符串数组 |
isDev | bool | true 写入 autoload-dev |
SetProperty
| 参数 | 类型 | 说明 |
|---|---|---|
property | string | 仅支持 name/description/type/keywords/homepage/license/minimum-stability/prefer-stable |
value | interface{} | 与属性匹配的值类型 |
示例
程序化初始化一个新项目
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)
}
// 从空配置开始
cj := &composer.ComposerJSON{}
cj.Name = "acme/widget"
cj.Description = "一个超棒的小部件库"
cj.Type = "library"
cj.License = "MIT"
cj.Authors = []map[string]string{
{"name": "Acme Team", "email": "dev@acme.io"},
}
// 写出初始 composer.json
if err := comp.WriteComposerJSON(cj); err != nil {
log.Fatal(err)
}
// 追加依赖与脚本
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", "运行测试套件"); err != nil {
log.Fatal(err)
}
// PSR-4 自动加载
if err := comp.AddAutoload("psr-4", "Acme\\Widget\\", "src/", false); err != nil {
log.Fatal(err)
}
fmt.Println("✅ composer.json 已生成")
}读取并修改 config
cj, err := comp.ReadComposerJSON()
if err != nil {
if err == composer.ErrComposerJSONNotFound {
log.Fatal("当前目录没有 composer.json")
}
log.Fatal(err)
}
fmt.Printf("项目: %s\n", cj.Name)
// 改进程超时
if err := comp.SetConfig("process-timeout", 600); err != nil {
log.Fatal(err)
}
// 读回
v, _ := comp.GetConfig("process-timeout")
fmt.Printf("process-timeout = %v\n", v)用便利函数只读查询
// 轻量结构,适合只读场景
data, err := comp.ReadComposerJson()
if err != nil {
log.Fatal(err)
}
fmt.Printf("直接依赖: %d\n", len(data.Require))
lock, err := comp.ReadComposerLock()
if err != nil {
log.Fatal(err)
}
fmt.Printf("已安装包: %d\n", len(lock.Packages))按绝对路径读取(不依赖工作目录)
data, err := composer.ReadComposerJsonFile("/srv/apps/myapp/composer.json")
if err != nil {
log.Fatal(err)
}
fmt.Println(data.Name)进阶
两套读 API 的区别
ReadComposerJSON()(composer_json.go)→*ComposerJSON,字段全、可写回,支持 mock。ReadComposerJson()(convenience.go)→*ComposerJsonData,字段更聚焦于查询,无 mock 支持。
需要 WriteComposerJSON 时用前者;只读统计时用后者更轻量。
AddRequire 不执行安装
AddRequire 只改 composer.json 文件,不会触发 composer install。要真正安装依赖,调用后需额外执行 comp.Install(false, false) 或 RequirePackage(走 composer require 命令)。
SetProperty 的硬编码白名单
SetProperty 用 switch 硬编码了支持的属性名。传 bin、authors 等未列出的属性会返回 unsupported property 错误。如需设置这些字段,请直接操作 *ComposerJSON 后调用 WriteComposerJSON。
测试用 Mock
SetMockComposerJSON / ClearMockComposerJSON 可让 ReadComposerJSON 返回注入的假数据,单测时无需真实文件。注意:便捷版的 ReadComposerJson 不受此 mock 影响。