真正共享依赖需满足同一name+同一version被多个包require,应解析composer.lock统计packages中require字段的反向引用次数,而非仅用composer show --tree或--no-dev命令提取包名。

composer show --tree 导出的是依赖树,不是共享依赖列表
很多人以为 composer show --tree 能直接列出“被多个包共用的依赖”,其实它只展示当前项目所有依赖的嵌套结构,不带复用统计。真正要找“哪些包被多个 require 同时拉进来”,得靠分析 composer.lock 里的 packages 和 packages-dev,再按 name 去重计数。
用 composer show + awk 提取并统计重复依赖(Linux/macOS)
最轻量、不依赖额外工具的方式是解析 composer.lock 的 JSON 结构,但更稳的实操路径是先用 Composer 自带命令导出扁平列表,再用 shell 统计:
-
composer show --format=json | jq -r '.[].name'只能拿到顶层包名,漏掉嵌套依赖 - 正确做法:运行
composer depends --tree --no-dev查看某个包被谁依赖,但没法批量扫描 - 推荐组合:
composer show --no-dev | awk '{print $1}' | sort | uniq -c | sort -nr | grep -v '^\s*1 '—— 这行命令提取所有非 dev 包名,排序后统计出现次数,过滤掉只出现 1 次的(即非共享依赖)
注意:composer show 默认只显示顶层 require,想包含全部传递依赖,必须先执行 composer install --no-dev 确保 lock 文件完整,否则结果不准。
PHP 脚本解析 composer.lock 才能准确识别“真正共享”的包
Shell 方式容易误判(比如同名但不同版本算作不同包),而 composer.lock 中每个包条目含 name 和 version,共享依赖必须满足“同一 name + 同一 version 被多个父包引用”。这时需解析 lock 文件的 packages 数组,并检查每个包的 require 字段反向映射。
- 关键点:
composer.lock里没有直接存“谁 require 了我”,得遍历所有包的require键,把vendor/name当 key 存入计数数组 - 示例逻辑片段(可保存为
find-shared.php):$lock = json_decode(file_get_contents('composer.lock'), true); $shared = []; foreach ($lock['packages'] as $pkg) { foreach ($pkg['require'] ?? [] as $req => $ver) { $shared[$req] = ($shared[$req] ?? 0) + 1; } } arsort($shared); foreach ($shared as $name => $count) { if ($count > 1) echo "$count\t$name\n"; } - 这个脚本统计的是“被多少个其他包显式 require”,不是安装时实际加载的实例数;若某包被 A 和 B 同时 require,但 A 锁死 v1.2.0、B 锁死 v1.3.0,则不会出现在结果里——这才是真正的“共享”含义
为什么 vendor/autoload.php 里没加载的包也可能算共享依赖?
Composer 的自动加载机制只管类文件路径映射,不管依赖是否真被运行时加载。一个包是否“共享”,只取决于 composer.lock 中它的 name 和 version 是否在多个 require 字段中出现过。即使某个包只被 require-dev 中的测试工具用到,只要它和生产环境某个包用了相同版本,就算共享。
最容易被忽略的是:dev 包和 prod 包如果共用同一个依赖版本,--no-dev 模式下该依赖仍会被视为共享——因为 composer.lock 是全量快照,统计必须基于 lock 文件而非当前 install 模式。


















