不配 symlink 选项,path 仓库默认就是复制,改本地代码不会生效——这是 composer 为保障构建可重现性做的保守设计;必须显式配置 "options": {"symlink": true}(在主项目 repositories 或本地包 composer.json 中),并执行 composer update vendor/name 才能创建符号链接,且需验证 composer.lock 中 source.type 为 "path"、系统权限允许 symlink、版本 ≥2.2。

直接上结论:不配 symlink 选项,path 仓库默认就是复制,改本地代码根本不会生效——这不是 bug,是 Composer 故意设计的保守行为。
为什么 vendor 里是普通文件夹,不是符号链接?
Composer 对 path 类型仓库默认禁用软链,只在显式声明后才尝试创建。它优先保障构建可重现性,而非开发便利性。
- 检查是否真启用了 symlink:运行
ls -la vendor/vendor-name/package-name(macOS/Linux)或dir vendor\vendor-name\package-name(Windows),输出中必须含->才算成功 - 启用方式有两种,且**必须选其一**:
— 在主项目composer.json的repositories条目里加"options": { "symlink": true }
— 或在本地包自己的composer.json根级加"options": { "symlink": true }(注意:不是主项目配置) - Windows 用户必须以管理员身份运行终端,或已开启“开发者模式”,否则
symlink会静默失败并 fallback 到 copy 模式 - Composer 版本需 ≥ 2.2;旧版本忽略
symlink配置,且不报错
composer install 不创建 symlink?必须用 update
composer install 完全依赖 composer.lock,它不会重新解析仓库类型或触发 symlink 创建逻辑。只有 update 才会重走发现-解析-链接全流程。
- 首次引入:执行
composer update vendor-name/package-name(推荐精确更新,避免波及其他包) - 后续修改本地包代码后:无需再
update—— symlink 已存在,改源码即刻反映到项目中 - 如果改了本地包的
autoload映射(比如新增 PSR-4 命名空间),必须手动运行composer dump-autoload,否则自动加载器找不到新类 - 验证是否真走 path 模式:打开
composer.lock,搜索该包,确认"source": { "type": "path" },而不是"type": "git"
路径、包名、版本三者必须严丝合缝
哪怕只差一个字母或大小写,Composer 就会跳过 path 仓库,转头去 Packagist 找匹配的稳定版,然后报 “Could not find package”。
-
url是相对于主项目composer.json所在目录的路径,不能用./开头(Composer 不解析),推荐../my-package或绝对路径 - 本地包的
composer.json中name字段(如"acme/my-awesome-package")必须和主项目require中的键完全一致(包括 vendor 名、分隔符、大小写) - 版本必须是
dev-前缀(如"dev-main")或直接删掉version字段;写"1.0.0"会被忽略,即使本地有 tag - 目标目录下必须存在合法
composer.json,且不能是空文件或语法错误——Composer 遇到解析失败会静默跳过该仓库
改了代码却还是旧逻辑?缓存比你想象中顽固
软链建对了,路径配准了,版本也写了 dev-main,但运行结果没变——大概率是缓存层在捣鬼。
- PHP CLI 环境下,检查
opcache.enable_cli是否为1;设成0再试,否则php -f test.php会执行旧字节码 - Web 环境(如 PHP-FPM)需重启 FPM 进程,或调用
opcache_reset()清空 OPcache - IDE(如 PHPStorm)可能缓存了自动加载映射,需手动触发 “Reload project from composer.json”
- 本地包若用了
autoload-dev(比如tests/目录),主项目默认无法加载这些类——它们只在包自身执行composer install时注册;要让主项目也能用,得把测试类挪进autoload(非-dev)区
真正卡住人的从来不是 symlink 本身,而是它背后那三层缓存:Composer 的 autoload 映射、PHP 的 OPcache、IDE 的索引。每层都得单独确认,漏一个就白配。











