如何优雅对比Composer包当前版本与新版本的文件?
解决Composer插件中对比包文件变更的优雅方案
你可以直接复用Composer内置的核心API来获取包的文件信息,无需依赖临时目录安装的方式,完美适配私有仓库认证场景,具体实现如下:
核心思路
利用Composer的PackageInterface实例获取包的分发/源码地址,通过内置的DownloadManager下载包内容到临时目录,再遍历文件计算哈希值。这种方式会自动复用当前Composer环境的认证配置(包括私有仓库的token、密钥等),解决私有仓库访问问题。
完整代码实现
use Composer\Composer; use Composer\DependencyResolver\Operation\UpdateOperation; use Composer\EventDispatcher\EventSubscriberInterface; use Composer\Installer\PackageEvent; use Composer\Installer\PackageEvents; use Composer\Plugin\PluginInterface; use Composer\Downloader\DownloadManager; use Composer\Package\PackageInterface; use Composer\Util\Filesystem; class Plugin implements PluginInterface, EventSubscriberInterface { private Composer $composer; private DownloadManager $downloadManager; private Filesystem $filesystem; public function activate(Composer $composer, \Composer\IO\IOInterface $io): void { $this->composer = $composer; $this->downloadManager = $composer->getDownloadManager(); $this->filesystem = new Filesystem(); } public static function getSubscribedEvents(): array { return [ PackageEvents::POST_PACKAGE_UPDATE => 'handlePackageUpdate', ]; } public function handlePackageUpdate(PackageEvent $event): void { $operation = $event->getOperation(); assert($operation instanceof UpdateOperation); $initialFileHashes = $this->getPackageFileHashes($operation->getInitialPackage()); $targetFileHashes = $this->getPackageFileHashes($operation->getTargetPackage()); // 对比文件变更 $addedFiles = array_diff_key($targetFileHashes, $initialFileHashes); $removedFiles = array_diff_key($initialFileHashes, $targetFileHashes); $changedFiles = array_filter($targetFileHashes, function ($hash, $file) use ($initialFileHashes) { return isset($initialFileHashes[$file]) && $initialFileHashes[$file] !== $hash; }, ARRAY_FILTER_USE_BOTH); // 有变更时执行自定义逻辑 if (!empty($addedFiles) || !empty($removedFiles) || !empty($changedFiles)) { // 替换为你的业务代码,比如重新生成项目文件 echo "Package " . $operation->getTargetPackage()->getName() . " has file changes\n"; } } private function getPackageFileHashes(PackageInterface $package): array { $tmpDir = $this->filesystem->tempDir(sys_get_temp_dir(), 'composer-plugin-'); $fileHashes = []; try { // 直接下载包到临时目录,自动复用当前认证配置 $this->downloadManager->download($package, $tmpDir); // 遍历文件计算哈希 $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($tmpDir, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY ); foreach ($iterator as $file) { if ($file->isFile()) { $relativePath = str_replace($tmpDir . DIRECTORY_SEPARATOR, '', $file->getPathname()); $fileHashes[$relativePath] = md5_file($file->getPathname()); } } } catch (\Exception $e) { error_log("Failed to process package " . $package->getName() . ": " . $e->getMessage()); } finally { // 自动清理临时目录 $this->filesystem->removeDirectory($tmpDir); } return $fileHashes; } public function deactivate(Composer $composer, \Composer\IO\IOInterface $io): void {} public function uninstall(Composer $composer, \Composer\IO\IOInterface $io): void {} }
关键优势
- 原生适配私有仓库:无需额外配置,直接复用当前Composer的私有仓库认证信息
- 性能提升:跳过完整的Composer安装流程,仅下载包归档文件,速度更快
- 资源友好:临时目录自动清理,避免残留文件占用空间
- 稳定性更高:依赖Composer官方API,避免shell_exec带来的环境兼容性问题
注意事项
- 确保插件有创建、删除临时目录的权限
- 可根据需求优化哈希计算逻辑,比如只监控特定目录或文件类型
- 建议捕获下载过程中的异常,避免插件崩溃影响主流程
内容的提问来源于stack exchange,提问作者halloei
相关产品推荐
相关产品推荐

