Composer插件注册自定义仓库协议需在activate()中监听INIT事件,通过addRepositoryClass注册类并显式声明repositories中type为小写协议名(如"myproto"),确保findPackage()返回含合法dist.url和type的包实例。

Composer插件里怎么注册自定义仓库协议
Composer本身不支持git+ssh://以外的私有协议(比如myproto://),想让composer install识别并拉取这种地址,必须通过Plugin-API注入协议处理器。关键不是写个类,而是让Composer在RepositoryFactory初始化阶段就“看到”你的协议。
核心动作是监听Composer\Plugin\PluginEvents::INIT事件,在回调中调用$io->writeError()调试时确认插件已加载,再通过$composer->getPackage()->getRepositoryManager()->addRepositoryClass()注册自定义仓库类——但注意:这个方法只接受已知类型(如vcs、package),真正生效的是重写RepositoryManager::createRepository()逻辑。
- 必须在
activate()里用$composer->getEventDispatcher()->addListener()绑定事件,不能只靠PluginInterface::activate()执行初始化 - 自定义协议名必须全小写,
MyProto会失败,myproto才被RepositoryManager识别 - 协议前缀末尾不加冒号,配置里写
"type": "myproto",不是"type": "myproto:"
自定义Repository类必须实现哪些方法
继承Composer\Repository\VcsRepository看似省事,但实际会触发GitDriver检测失败——因为VcsRepository硬编码了git、hg等协议白名单。正确做法是直接实现Composer\Repository\InstalledRepositoryInterface和Composer\Repository\WritableRepositoryInterface,但更轻量的路径是扩展Composer\Repository\RepositoryInterface并专注findPackage()和getPackages()。
重点不是功能完整,而是让PoolBuilder能拿到PackageInterface实例。你的findPackage()返回值必须包含name、version、dist(含url和type),否则Downloader根本不会调用你。
-
dist.url必须是可直接下载的HTTP/HTTPS链接,不能是myproto://地址——协议转换逻辑必须在findPackage()内部完成 -
dist.type填zip或tar,对应ZipDownloader或TarDownloader,别填自定义类型 - 如果需要认证,把token塞进
dist.url查询参数里,不要依赖RemoteFilesystem的setAuthentication()——它不读你的协议
为什么composer update不触发自定义仓库的findPackage
现象是插件加载成功、composer.json里写了"type": "myproto",但composer update完全不调用你的findPackage(),甚至没报错。根本原因是Composer的RepositoryManager在构建Pool前,会先根据composer.json里的require字段反向推导需要哪些仓库,而你的自定义协议没出现在任何repositories配置的type值里。
围绕关键发现、作用机制、临床相关性及研究局限性展开讨论。适用于撰写或优化任何生物医学论文的“讨论(Discussion)”部分——包括结果解读、与既往文献关联、阐释意外发现、界定研究局限性,以及撰写结论。当用户输入以下任一指令时也会自动触发该功能: - “write my discussion” - “help me discuss my findings” - “how do I compare to prior studies” - “write the limitations par
必须显式声明仓库,哪怕只是占位:
{
"repositories": [
{
"type": "myproto",
"url": "myproto://dummy"
}
]
}
这个url值会被忽略,但type字段会让RepositoryManager实例化你的仓库类。漏掉这一步,整个流程根本不会走到你的代码。
- 即使项目只依赖一个包,也必须配
repositories,不能只靠require里的vendor/name触发 -
url可以是任意字符串,但不能为空或null,否则JsonValidationException会提前终止 - 多个自定义协议要分别声明,
"type": "myproto,another"不合法
Downloader如何适配非标准协议响应头
你的服务返回Content-Disposition: attachment; filename=package.zip,但ZipDownloader默认只认Content-Type: application/zip。它用RemoteFilesystem::get()下载后,会调用Util::isZip()校验文件头,如果服务返回text/plain或没设Content-Type,就会抛UnexpectedValueException:“The archive may contain malicious files”。
解决方案不是改Downloader,而是让服务响应头正确:
- 强制设置
Content-Type: application/zip(或application/x-tar),这是Util::isZip()和Util::isTar()的唯一判断依据 - 如果服务无法改响应头,可在Downloader里用
setOptions(['http' => ['ignore_errors' => true]])绕过校验——但风险自负 - 别试图用
stream_context_set_default()劫持HTTP请求,RemoteFilesystem内部用cURL,不走PHP流包装器
协议解析和包下载是两层事:插件解决“找得到”,Downloader解决“下得稳”。中间任何一环的响应头、状态码、文件头不匹配,都会静默失败。

















