如何编写一个Composer插件来修改包的下载URL? (自定义行为)

10次阅读

composer插件需实现PluginInterface并监听PRE_PACKAGE_INSTALL事件,通过setDistUrl()等方法修改包下载URL,同时同步更新dist.type和dist.shasum以确保校验通过;或通过Config::merge动态注入镜像仓库配置。

如何编写一个Composer插件来修改包的下载URL? (自定义行为)

Composer插件必须实现 PluginInterface 并监听 PRE_PACKAGE_INSTALL 事件

Composer 插件不能直接“修改下载 URL”,因为包元数据(如 dist.url)在加载后是只读的。真正可行的路径是:在包安装前拦截请求,用自定义逻辑替换源地址。这需要监听 PRE_PACKAGE_INSTALL 事件,并通过修改 PackageEvent::getOperation()->getPackage() 的 dist 配置来生效——但注意,该对象CompletePackage 实例,其 setDistUrl() 方法存在且可用。

关键点:

  • 插件类必须实现 ComposerPluginPluginInterface
  • 必须在 activate() 中获取 EventDispatcher 并添加监听器
  • PRE_PACKAGE_INSTALLPRE_PACKAGE_UPDATE 事件允许安全修改 package 对象
  • 不能用 POST_* 事件,此时下载流程已启动,修改无效

setDistUrl() 修改后需同步更新 dist.shasumdist.type

单纯调用 $package->setDistUrl('https://...') 不足以让 Composer 正常下载。它会校验 dist.shasum,若不匹配则报错 Invalid archive signature;若未设 dist.type,可能 fallback 到 VCS 拉取,绕过你的 URL。

实操建议:

  • 确保新 URL 返回的 ZIP 包与原始包内容一致(否则 shasum 校验失败)
  • 显式设置 $package->setDistType('zip')(或 'tar'
  • 从原包读取 $package->getDistShaSums()['sha256']['sha1'],再用相同值调用 $package->setDistShaSums([...])
  • 如果新 URL 是镜像服务(如私有 Packagist),它应返回标准 Composer dist 响应头(Content-MD5 等非必需,但 shasum 必须一致)

插件中无法直接改写 repositories 配置,但可通过 Config 动态注入镜像

有些场景想全局把所有 packagist.org 请求转到内网镜像,这时不应在每个包上 patch URL,而应修改 Composer 运行时的仓库配置。插件可通过 $composer->getConfig() 获取 Config 实例,再调用 merge() 注入自定义 repositories

示例逻辑:

public function activate(Composer $composer, IOInterface $io) {     $config = $composer->getConfig();     $repos = $config->get('repositories') ?: [];     array_unshift($repos, [         'type' => 'composer',         'url'  => 'https://my-mirror.example.com',         'canonical' => false,     ]);     $config->merge(['repositories' => $repos], Config::SOURCE_PLUGIN); }

注意:

  • 必须设 'canonical' => false,否则 Composer 会忽略其他仓库
  • 此方式优先级高于 composer.json 中的 repositories,但低于命令行 --repository
  • 不会影响已 lock 的包来源,仅对后续 resolve 生效

调试时用 COMPOSER_MEMORY_LIMIT=-1 COMPOSER_VERBOSITY=4 查看真实 dist URL

URL 是否被成功替换,最可靠的方式不是看日志文字,而是观察 Composer 实际发起的 HTTP 请求。启用高 verbosity 后,你会在输出中看到类似:

Downloading https://my-mirror.example.com/packagist/phpunit/phpunit/9.6.0.0/dist.zip

常见踩坑点:

  • 忘记在 composer.jsonautoload 中声明插件类的 PSR-4 映射,导致类找不到
  • 插件未声明 "type": "composer-plugin",Composer 直接跳过加载
  • 用了 POST_PACKAGE_INSTALL 事件,代码执行了但无效果(URL 已下载完毕)
  • 新 URL 返回 302 重定向但未带 Content-Length,触发 Composer 的 chunked transfer bug(v2.5+ 已修复,旧版建议避免重定向)

实际部署前,务必在干净环境运行 composer update --no-plugins 对比一次,确认差异仅来自你的插件逻辑。

text=ZqhQzanResources