Composer缓存清理不能只靠composer clear-cache,因为它无差别清空整个缓存目录,不识别“是否被当前项目依赖”或“是否仍在vendor中存在”,需通过扫描所有本地项目的composer.json提取require列表,再与缓存中实际存在的包做差集来精准识别并删除未被任何项目引用的 stale 包。

Composer缓存清理为什么不能只靠 composer clear-cache
因为 composer clear-cache 是清空整个缓存目录(比如 ~/.composer/cache),不区分“已安装但未引用”或“从未被任何项目 require 过”的包。实际中,大量缓存是历史项目残留的旧版本 ZIP、dist 包、metadata 和 zip 临时文件,它们占空间却毫无用处——但 clear-cache 不会判断是否“被当前项目依赖”,更不会识别“是否还在 vendor/ 中存在对应安装路径”。
如何识别“不再被任何项目引用”的缓存包
Composer 缓存本身没有元数据记录“哪个项目曾用过它”,所以必须反向扫描:遍历所有本地 Composer 项目(composer.json 所在目录),提取它们的 require 和 require-dev 列表,解析出完整包名+约束版本(如 "monolog/monolog": "^2.8"),再映射到缓存中实际存在的 dist ZIP 或 source git clone 目录。没被任何项目显式声明的,才属于可清理范围。
- 扫描路径需覆盖常见位置:
~/projects、/var/www、~/Sites,避免硬编码单一路径 - 跳过
vendor/子目录,防止重复扫描或递归陷入死循环 - 忽略
composer.lock—— 它只反映锁定时的状态,不能代表当前依赖关系;以composer.json的require字段为准 - 注意处理带版本别名的包(如
"phpunit/phpunit": "9.6.x-dev"),需解析为规范包名phpunit/phpunit,否则匹配失败
定时脚本怎么写才安全不误删
直接 rm -rf ~/.composer/cache 风险太高,尤其当多个项目正在运行 composer install 时。推荐分三步走:先生成待删列表(dry-run),再确认无误后执行删除,最后记录日志供回溯。
#!/bin/bash
CACHE_DIR="$HOME/.composer/cache"
SCAN_ROOTS=("$HOME/projects" "/var/www")
<h1>提取所有活跃包名(去版本号)</h1><p>find "${SCAN<em>ROOTS[@]}" -name 'composer.json' -not -path '<em>/vendor/</em>' -exec grep -oE '"[a-z0-9</em>.]+/[a-z0-9_.]+":' {} \; | \
sed 's/["$":]//g' | sort -u > /tmp/composer-active-packages.txt</p><h1>找出缓存中所有包目录名(dist/source 下的 vendor/name 格式)</h1><p>find "$CACHE_DIR/dist" "$CACHE_DIR/files" -mindepth 2 -maxdepth 2 -type d 2>/dev/null | \
awk -F'/' '{print $(NF-1) "/" $NF}' | sort -u > /tmp/composer-cache-packages.txt</p><h1>差集:缓存中有但未被任何项目 require 的包</h1><p>comm -13 /tmp/composer-active-packages.txt /tmp/composer-cache-packages.txt | \
while read pkg; do
echo "DELETE: $CACHE_DIR/dist/$pkg"
echo "DELETE: $CACHE_DIR/files/$pkg"
done | tee /tmp/composer-stale-cache.log</p><h1>真正删除前加 5 秒确认(生产环境建议注释掉这行,改为人审)</h1><p>read -t 5 -p "Will delete above paths. Press Ctrl+C to abort..." || true</p><h1>执行删除(仅限明确列出的路径)</h1><p>sed 's/^DELETE: //' /tmp/composer-stale-cache.log | xargs -r -d '\n' rm -rf</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill10050" title="Academic Composer Upload"><img
src="https://img.php.cn/upload/skill/001/246/273/6adaf20d79f23518.jpg" alt="Academic Composer Upload" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill10050" title="Academic Composer Upload" class="overflowclass">Academic Composer Upload</a>
<p class="overflowclass">辅助学术写作,包括查找资料、生成大纲、引用扩写及润色风格,同时确保引用规范。</p>
</div>
<a rel="nofollow" href="/xiazai/skill10050" title="Academic Composer Upload" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
关键点:comm -13 做有序差集,比 grep -vFf 更可靠;xargs -r -d '\n' 防止包名含空格或特殊字符出错;tee 同时输出到终端和日志,方便事后核查。
crontab 设置与权限陷阱
用当前用户 crontab(crontab -e)而非 root,否则 ~/.composer/cache 路径会指向 root 用户目录。同时注意 shell 环境差异:cron 默认 PATH 很窄,find 和 awk 可能找不到,务必用绝对路径或显式设置 PATH。
- 推荐写法:
PATH=/usr/bin:/bin /home/youruser/bin/clean-composer-cache.sh - 避免使用
~:cron 不展开波浪线,$HOME更稳妥 - 不要每分钟跑一次——缓存变化频率低,每周一次足够,例如:
0 3 * * 1(每周一凌晨 3 点) - 如果脚本涉及 PHP 扩展或自定义 bin,确保 cron 环境加载了你的 shell profile(如
. ~/.bashrc),否则composer命令可能不可用
真正麻烦的是跨项目共享缓存场景(比如 Docker 容器挂载 host cache 目录),这时脚本得额外检查 COMPOSER_CACHE_DIR 环境变量是否被覆盖,否则扫错目录。










