跨磁盘时symlink必然失败,因操作系统限制;Composer静默fallback至copy模式,需手动update同步修改。

path 依赖跨磁盘时 symlink 必然失败
Composer 的 path 类型仓库在跨 Windows 或 Linux 不同挂载点(如 /mnt/data 和 /home)时,symlink 操作会直接失败——不是配置问题,是操作系统级限制。Linux 下 ln -s 允许跨分区建软链接,但 Composer 内部用的是 symlink() 系统调用,它只支持同一文件系统内创建;Windows 的 mklink /D 更严格,跨卷根本不可用。
常见错误现象:Creating symlink for vendor/name failed, falling back to copy —— 这行日志极容易被刷屏忽略,尤其没加 -v 参数时。
- 检查方式:运行
composer update -v vendor/name,搜falling back - 验证是否真跨分区:用
df -h .和df -h ../my-local-package对比挂载点 - 不要指望改
config.symlink = true能起作用,它连失败都不报错,只会静默降级
硬复制(copy)是跨分区下的唯一可靠模式
Composer 对 path 依赖的 fallback 行为是安全的:当 symlink 失败,它会自动执行完整目录拷贝(recursive copy),保留所有文件、权限和 composer.json 元数据。你看到的 vendor/vendor/name 是真实文件夹,不是链接,ls -la 里没有 -> 符号。
- 这个 copy 是单向的:本地包修改后,必须手动
composer update vendor/name才能同步,不会自动监听文件变化 - 如果想强制走 copy(比如避免误配 symlink 导致行为不一致),在项目
composer.json的config段显式写"symlink": false - 注意:已存在的 symlink 不会被自动转成 copy,必须先
rm -rf vendor/vendor/name再重装
替代方案:用符号链接包裹路径,而非依赖本身
如果你坚持要“实时感知”跨盘本地包,又不想每次 update,可以绕过 Composer 的 symlink 机制,在文件系统层做一层间接:
- 在项目同盘符下建一个空目录,比如
./local-packages/my-package - 用系统命令建跨盘软链:Linux 上
ln -s /mnt/data/my-package ./local-packages/my-package;Windows 上以管理员身份运行mklink /D .\local-packages\my-package D:\data\my-package - 然后在
composer.json的repositories里指向./local-packages/my-package - 这样 Composer 认为路径在同一文件系统,
symlink: true就能生效,实际文件仍跨盘
这个技巧有效,但代价是部署时需确保目标机器也存在对应挂载点和权限,CI/CD 中容易出错。
为什么 vendor/bin 下的二进制不受影响?
vendor/bin 里的脚本(如 phpunit、psalm)是 Composer 自己生成的包装器,它们内部用相对路径引用 vendor/vendor-name/package/bin/tool,和 path 依赖的 symlink 机制完全无关。即使你的本地包是跨盘 copy 进来的,只要它 composer.json 里正确声明了 bin 字段,vendor/bin/tool 就能正常工作。
真正容易被忽略的是:跨盘 copy 后,包内如果有通过 __DIR__ 或 realpath() 推导自身路径的 PHP 代码,可能因物理位置变化而失效——这不是 Composer 的问题,是包自身设计缺陷。











