post-autoload-dump 是清理失效 symlink 的唯一可靠时机,因 Composer 仅在此钩子触发时已重写 autoload 文件、symlink 已就位或失效且自动加载器就绪,可安全校验;其他钩子如 post-install-cmd 或 post-update-cmd 均存在执行时机不可靠、可能误删有效链接等问题。

post-autoload-dump 是清理失效 symlink 的唯一可靠时机
因为 Composer 只在真正重写 vendor/autoload.php 及其映射文件(如 autoload_psr4.php)后才触发该钩子,此时所有 path 类型包的 symlink 已就位或已失效,且自动加载器已就绪,可安全调用 is_link() 和 readlink() 判断链接有效性。
而 post-install-cmd 可能在 symlink 创建前就执行,post-update-cmd 更不可靠——它甚至可能跳过 symlink 更新(比如 lock 文件未变时),导致你删掉一个其实还有效的链接。
- 必须把清理逻辑放在
"post-autoload-dump"下,不能用pre-前缀或其它钩子 - 不要在钩子里调用
composer dump-autoload,会递归触发自身,死循环 - PHP 脚本路径需相对项目根目录,例如
@php scripts/cleanup-symlinks.php
怎么判断 symlink 是否失效?别只看 file_exists()
file_exists() 对失效 symlink 返回 false,但也会对权限不足、跨挂载点等场景误判;更稳妥的是组合判断:is_link() + readlink() + realpath() 是否为 false。
尤其要注意 path 类型包:如果本地包被移动、重命名或 composer.json 中的 autoload 字段改了但没重新 dump,symlink 就会指向一个不存在的路径,但 file_exists() 可能返回 true(因链接本身存在)。
- 正确检查方式:
if (is_link($path) && !realpath($path)) { unlink($path); } - 避免用
shell_exec('ls -la')解析输出,跨平台不稳,且容易漏掉权限拒绝的 case - 慎用
clearstatcache():仅在循环大量检查前调用一次即可,否则拖慢流程
Windows 下 symlink 清理要额外注意权限和类型
Windows 默认不支持普通用户创建 symlink,Composer 在 Windows 上对 path 包实际使用的是 junction(目录符号链接)或复制(取决于 config.use-symlinks 设置)。这意味着 is_link() 在某些配置下会返回 false,但 junction 仍可能失效。
如果你在 Windows CI 环境中发现清理不生效,大概率是 use-symlinks 被设为 false,此时 vendor 下是真实副本而非链接——那就不该删,而是该清缓存或重装。
- 先确认当前行为:
composer config --global use-symlinks,输出true才走 symlink 路径 - junction 失效时,
realpath()同样返回false,所以上述判断逻辑仍适用 - 不要用
del /q或rd /s /q直接删 vendor 下疑似目录——可能删掉真实包
清理脚本示例:只删 path 包产生的失效链接
比起在 composer.json 里堆 shell 命令,用 PHP 脚本更可控。下面这个 scripts/cleanup-symlinks.php 只扫描 vendor/ 下由 Composer 自动创建的 path 包链接(即名称不含 /、且目标路径以 ../ 开头的链接):
<?php
$vendor = __DIR__ . '/../vendor';
foreach (scandir($vendor) as $dir) {
$path = $vendor . '/' . $dir;
if (!is_dir($path) || $dir === '.' || $dir === '..') continue;
foreach (scandir($path) as $sub) {
$subpath = $path . '/' . $sub;
if (!is_link($subpath)) continue;
$target = @readlink($subpath);
if ($target && str_starts_with($target, '../') && !realpath($subpath)) {
echo "Removing broken symlink: $subpath\n";
@unlink($subpath);
}
}
}
这个逻辑绕开了 vendor/composer/installed.json 解析,也不依赖外部命令,兼容 Linux/macOS/Windows,且只动明确属于 path 包的链接——不会误伤 vendor/bin 下的脚本链接或其他工具生成的符号链接。
真正容易被忽略的是:失效 symlink 往往不是孤立问题,而是本地包路径变更、Git checkout 切换分支、或 composer.json 中 repositories 配置临时注释导致的连锁反应。清理只是表象,得结合 composer update --no-cache 和 rm -rf ~/.composer/cache/vcs/* 一起做,才能断根。


















