VichUploader:更新Asset实体分类时重传图片至新目录并更名
解决方案:分类变更时自动迁移S3文件并更新文件名
嘿,这个场景我之前帮同事处理过,刚好适配你的情况——不用改EasyAdmin的默认Form或控制器,靠Doctrine事件监听器结合VichUploader的内置服务就能搞定自动迁移,下面一步步来:
1. 写个Doctrine事件监听器,盯着分类变更
咱们需要监听Asset实体的preUpdate事件(保存前触发),一旦检测到分类字段变了,就执行文件迁移和文件名更新。创建一个监听器类:
// src/EventListener/AssetFileMigrationListener.php namespace App\EventListener; use App\Entity\Asset; use Doctrine\ORM\Event\PreUpdateEventArgs; use Vich\UploaderBundle\Mapping\PropertyMappingFactory; use Vich\UploaderBundle\Storage\StorageInterface; use Vich\UploaderBundle\Naming\NamerInterface; class AssetFileMigrationListener { public function __construct( private StorageInterface $storage, private PropertyMappingFactory $propertyMappingFactory, private NamerInterface $assetImageNamer // 你的自定义文件名命名器 ) {} public function preUpdate(PreUpdateEventArgs $args): void { $entity = $args->getObject(); // 只处理Asset实体,且分类字段确实改了 if (!$entity instanceof Asset || !$args->hasChangedField('category')) { return; } $oldCategory = $args->getOldValue('category'); $newCategory = $args->getNewValue('category'); // 获取Vich对image字段的配置映射 $mapping = $this->propertyMappingFactory->getMapping($entity, 'image'); if (!$mapping) { return; } $oldFilename = $entity->getImageName(); // 假设你的实体有这个getter if (!$oldFilename) { return; // 没文件的话直接跳过 } // 用你的自定义命名器生成新文件名(自动包含新分类的文件夹) $newFilename = $this->assetImageNamer->name($entity, $mapping); // S3没有真正的"移动",只能先复制到新路径,再删旧文件 try { // 复制文件到新分类目录 $this->storage->copy($entity, 'image', $newFilename); // 删除旧路径的文件 $oldFilePath = $this->storage->resolvePath($entity, 'image'); $this->storage->remove($oldFilePath); } catch (\Exception $e) { // 这里可以加日志,或者抛异常中断更新,避免数据不一致 throw new \RuntimeException('迁移资产文件失败:' . $e->getMessage()); } // 更新实体的文件名字段 $entity->setImageName($newFilename); // 告诉Doctrine这个字段变了,要保存到数据库 $args->getEntityManager()->getUnitOfWork()->recomputeSingleEntityChangeSet( $args->getEntityManager()->getClassMetadata(Asset::class), $entity ); } }
2. 把监听器注册到服务容器
在config/services.yaml里配置一下,让Doctrine能找到这个监听器:
services: App\EventListener\AssetFileMigrationListener: arguments: $storage: '@vich_uploader.storage.s3' # 对应你的S3存储服务ID $assetImageNamer: '@your.custom.asset.image.namer' # 你的自定义命名器服务ID tags: - { name: doctrine.event_listener, event: preUpdate }
3. 确保Vich配置和实体字段正确
你的Asset实体得有imageName字段(存文件名),而且Vich的配置要正确关联分类目录。比如你的vich_uploader.yaml大概是这样:
# config/packages/vich_uploader.yaml vich_uploader: db_driver: orm storage: s3 mappings: asset_image: uri_prefix: 'https://你的存储桶.s3.amazonaws.com/assets' upload_destination: 's3://你的存储桶/assets' namer: your.custom.asset.image.namer # 你的自定义文件名命名器 directory_namer: App\Naming\AssetDirectoryNamer # 按分类生成文件夹的目录命名器
这里的directory_namer就是你之前写的那个,负责根据分类名称生成文件夹路径,这样新文件名会自动带上新分类的目录。
几个关键注意点
- S3权限:确保你的应用服务器有S3的复制、删除权限,不然迁移会失败。
- 变更集重新计算:因为我们在
preUpdate里改了imageName,必须调用recomputeSingleEntityChangeSet,不然Doctrine不会把这个变更保存到数据库。 - 异常处理:一定要加try-catch,不然文件迁移失败会导致实体更新成功但文件没动,数据就不一致了。
测试一下
- 新建Asset选分类A,上传文件,去S3看是不是在
assets/A/xxx.jpg - 编辑Asset改成分类B,保存
- 检查S3:旧路径的文件没了,新路径
assets/B/新文件名.jpg存在 - 查数据库:Asset的
imageName已经变成新文件名
完美,这样就实现了分类变更时自动迁移文件和更新文件名,而且完全兼容EasyAdmin的默认编辑流程,不用改任何Form或控制器。
内容的提问来源于stack exchange,提问作者misha
相关产品推荐
相关产品推荐

