
本文详解为何使用本地Composer仓库(如Satis)时composer install仅安装顶层require/require-dev包而忽略其子依赖,并揭示关键原因:仓库元数据缺失(如conflict、replace、provide等字段未导出),导致Composer无法正确解析依赖图。
本文详解为何使用本地composer仓库(如satis)时`composer install`仅安装顶层`require`/`require-dev`包而忽略其子依赖,并揭示关键原因:仓库元数据缺失(如`conflict`、`replace`、`provide`等字段未导出),导致composer无法正确解析依赖图。
在基于 Composer 的 PHP 项目中,composer install 的行为高度依赖于仓库提供的完整元数据,而非仅仅 ZIP 包内嵌的 composer.json 文件。你遇到的现象——删除 vendor 和 composer.lock 后执行 composer install,仅装入 require 和 require-dev 中声明的 11 个顶层包(如 laravel/framework、guzzlehttp/guzzle),却完全跳过它们各自所需的数百个子依赖(如 symfony/http-foundation、psr/container、illuminate/support 等)——根本原因并非网络或路径错误,而是本地仓库(如 Satis)生成的元数据不完整。
? 问题本质:Composer 不读取 ZIP 内的 composer.json,只信任仓库元数据
当你配置:
"repositories": [{ "type": "composer", "url": "http://localhost/" }]Composer 实际向 http://localhost/packages.json(或类似索引端点)发起请求,获取的是由 Satis 构建的 聚合元数据文件(packages.json),其中每个包条目都应包含其完整的依赖声明。但 Satis 默认不会自动提取并注入所有 composer.json 中的字段——它只导出你在 satis.json 中显式指定或通过 --skip-dev 等参数控制的字段。
你提到“每个 ZIP 包里都有 composer.json”,这没错;但 Composer 在安装阶段完全不解析 ZIP 内容,它只依据 packages.json 中该包条目的 require、conflict、replace、provide、suggest 等字段来构建依赖图。若这些字段缺失(例如 satis.json 中未启用 require 以外的字段导出),Composer 就会认为:“这个包没有依赖”,从而跳过安装任何子包。
工作流任务拆解与模型编排技能。使用场景:(1) 收到复杂工作任务需要拆解为可执行步骤,(2) 需要为不同步骤选择最合适的模型,(3) 需要跟踪工作流进度和模型使用情况,(4) 长时间任务卡住需要问题诊断和解决方案。
✅ 正确行为:
laravel/framework:^9.19条目在packages.json中必须包含类似"require": { "php": "^8.0", "ext-json": "*", "illuminate/collections": "^9.0", "symfony/http-foundation": "^6.0" }的完整声明。
❌ 错误表现:该条目仅有"name": "laravel/framework", "version": "9.52.15",无require字段 → Composer 认为它“零依赖”,直接跳过安装其全部子包。
✅ 解决方案:完善 satis.json 元数据导出配置
你需要确保 satis.json 显式声明导出所有关键依赖相关字段。以下是推荐的最小完备配置:
{
"name": "My Local Repository",
"homepage": "http://localhost",
"repositories": [
{ "type": "vcs", "url": "../my-packages/laravel-framework" },
{ "type": "vcs", "url": "../my-packages/guzzlehttp-guzzle" }
],
"require-all": true,
"archive": {
"directory": "dist",
"format": "zip",
"skip-dev": false
},
"output-dir": "web",
"providers": {
"minimum-stability": "stable",
"require-dependencies": true, // ← 关键!强制导出 require
"require-dev-dependencies": true, // ← 关键!导出 require-dev(对 dev 包)
"conflict-dependencies": true, // ← 必须!否则版本冲突无法识别
"replace-dependencies": true, // ← 必须!处理替代包逻辑
"provide-dependencies": true, // ← 必须!支持虚拟包(如 ext-* 模拟)
"suggest-dependencies": true // ← 推荐:提升可维护性
}
}? 提示:
"require-all": true表示包含所有仓库中的包;"require-dependencies": true是解决你问题的核心开关——它告诉 Satis:请把每个包composer.json中的require字段完整写入packages.json。
构建后,检查生成的 web/packages.json,搜索 laravel/framework,确认其某个版本条目中已包含完整的 require 对象,而非空或缺失。
⚠️ 补充注意事项与最佳实践
-
不要依赖 ZIP 内
composer.json:即使你手动解压 ZIP 并验证其内容正确,Composer 在install阶段也绝不会读取它。一切以packages.json元数据为准。 -
验证方式:运行
composer install -vvv,观察日志中是否出现Resolving dependencies through SAT及后续大量Downloading ...—— 若只看到 11 个包下载且很快报Class not found,说明依赖图未构建。 -
CI/CD 安全建议:本地仓库构建后,用
composer validate --strict检查packages.json格式;并添加自动化测试:composer create-project --repository-url=http://localhost myproject && cd myproject && composer install && php artisan --version。 -
替代方案(轻量级):若无需私有 Packagist 级功能,可直接使用
path仓库类型(需严格匹配name/version/symlink),它绕过元数据层,直接读取本地composer.json,但仅适用于开发环境。
? 总结
| 现象 | 根本原因 | 修复动作 |
|---|---|---|
composer install 仅装顶层包,子依赖全丢失 |
Satis 未导出 require/conflict/provide 等元数据字段 |
在 satis.json 中启用 require-dependencies, conflict-dependencies, provide-dependencies 等开关 |
Illuminate\Foundation\Application not found |
laravel/framework 的 require 未被识别 → illuminate/support 等未安装 |
确保 packages.json 中 laravel/framework 条目含完整 require 块 |
| 本地包 ZIP 正确但行为异常 | Composer 安装阶段不读 ZIP,只信 packages.json
|
放弃“ZIP 有就安全”思维,专注元数据完整性 |
完成配置后,重新运行 php bin/satis build satis.json web,清除本地 Composer 缓存(composer clear-cache),再执行 composer install —— 你将看到完整的依赖树被正确解析与安装,artisan 命令也能正常执行。

















