因为Composer的path仓库默认启用软链接模式(symlinks: true),它不检查源目录变更,只按版本约束判断是否跳过更新;需设"options": {"symlinks": false}或手动remove+require才能同步代码。

为什么 path 仓库改了代码,composer update 却不拉新版本?
因为 Composer 的 path 仓库默认启用“软链接模式”(symlinks: true),它不会复制代码,而是直接链接到本地目录。这意味着:composer update 根本不检查源目录有没有变化——它只看 composer.json 里声明的版本约束是否满足,满足就跳过安装/更新。
常见现象:你改了 vendor/myorg/mypackage/src/Helper.php,运行 composer update myorg/mypackage 后文件内容没变,甚至 git status 在源目录里都显示已提交,但项目里还是旧逻辑。
- 根本原因不是缓存,也不是权限,是 Composer 假设你手动维护路径包,不主动同步
- 只有首次
composer install或显式触发重安装时,才会重新解析 symlink - 如果你在
path源目录里切了 git 分支、拉了新 commit,链接指向的仍是原路径下的当前工作区内容——但 Composer 不感知这些变更
如何让 path 包随源目录变更实时生效?
两个可靠方式,按使用场景选:
-
开发调试阶段(推荐):关掉 symlink,强制每次用
cp -r复制 —— 在composer.json的仓库配置里加"options": {"symlinks": false} -
持续集成或多人协作测试:保留 symlink,但每次改完源码后手动触发重安装:
composer remove myorg/mypackage && composer require myorg/mypackage:@dev - 注意:
@dev必须和path仓库的"minimum-stability"及"prefer-stable"配置兼容,否则会报Could not find package
示例 repositories 配置:
{
"type": "path",
"url": "../mypackage",
"options": {
"symlinks": false
}
}
path 仓库的版本号到底怎么起作用?
很多人以为写 "version": "dev-main" 就能控制分支,其实不对——path 类型仓库的版本完全由其内部 composer.json 决定,外部 require 中的版本约束仅用于匹配,不驱动检出。
- 如果你的
../mypackage/composer.json里写的是"version": "1.0.0",那无论你在主项目中写"myorg/mypackage": "^1.0"还是"dev-main",只要满足,就链接过去 - 想按分支加载?必须在
../mypackage/composer.json里删掉version字段,并确保有"branch-alias": {"dev-main": "1.0.x-dev"},再配合"myorg/mypackage": "1.0.x-dev" - 没有
version字段时,Composer 会 fallback 到 git 当前 HEAD 的 commit hash 作为版本标识,但这个 hash 不会自动刷新,除非你composer update时加-v看日志,发现它实际用了新 hash
容易被忽略的陷阱:autoload 和 symlink 的冲突
当启用 symlinks: true 且源目录含自定义 autoload(如 psr-4 映射到 src/),而你的主项目又执行过 composer dump-autoload,就可能出现类找不到,或加载了旧版本类文件。
- 原因:Composer 的 autoloader 缓存(
vendor/autoload.php)记录的是 symlink 目标路径,但 OPCache 或 IDE 可能缓存了旧的 realpath - 验证方法:在代码里加
echo (new ReflectionClass(MyClass::class))->getFileName();,看输出路径是否指向你刚修改的源目录 - 临时解决:
composer dump-autoload -o && php -r "opcache_reset();";长期建议开发期统一用"symlinks": false
最稳的做法其实是:本地开发路径包,一律配 "symlinks": false,CI 流水线再切回 true——毕竟没人希望 debug 两小时才发现问题出在 autoload 缓存没清干净。


















