你是正确的,问题的根源在于
UploadListener
监听
prePersist
和
preUpdate
,而
LoggableListener
监听
onFlush
。由于
onFlush
在
preUpdate
之前触发,因此文件更改永远不会被记录。可以通过以下几个步骤来解决这个问题。
1. 创建新的 UploadListener
首先,你可以编写自己的 UploadListener 来监听 onFlush
。
namespace App\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Events;
use Vich\UploaderBundle\EventListener\Doctrine\UploadListener;
class VichUploadListener extends UploadListener
{
public function onFlush(OnFlushEventArgs $args): void
{
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityUpdates() as $entity) {
$this->preUpdate(new LifecycleEventArgs($entity, $em));
}
foreach ($uow->getScheduledEntityInsertions() as $entity) {
$this->preUpdate(new LifecycleEventArgs($entity, $em));
}
}
public function getSubscribedEvents(): array
{
return [Events::onFlush];
}
}
在这个例子中,我重用了原来的
UploadListener
以使事情更容易。由于我们正在监听
onFlush
,因此重要的是在文件上传后重新计算实体更改集,这就是为什么我对预定更新和插入都使用了“preUpdate”方法的原因。
当更改此类事件时,您必须小心。如果您有另一个侦听器期望设置(或取消设置)其中一个文件字段的值,则可能会更改预期行为。如果您使用第二个foreach来处理新上传,则尤其如此。
prePersist
在
onFlush
之前触发,因此这将使新上传比以前晚设置。
2. 创建新的CleanListener
接下来,我们现在必须创建一个新的
CleanListener
。如果设置了
delete_on_update
,则此侦听器在更新文件字段时删除旧文件。由于它监听
preUpdate
,因此我们必须将其更改为
onFlush
,以便正确删除旧文件。
namespace App\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Events;
use Vich\UploaderBundle\EventListener\Doctrine\CleanListener;
class VichCleanListener extends CleanListener
{
public function onFlush(OnFlushEventArgs $args): void
{
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityUpdates() as $entity) {
$this->preUpdate(new LifecycleEventArgs($entity, $em));
}
}
public function getSubscribedEvents(): array
{
return [Events::onFlush];
}
}
3. 配置新的监听器
现在,我们需要使用刚刚编写的监听器来覆盖配置中的默认监听器。
services:
vich_uploader.listener.upload.orm:
class: 'App\EventListener\VichUploadListener'
parent: 'vich_uploader.listener.doctrine.base'
autowire: false
autoconfigure: false
public: false
vich_uploader.listener.clean.orm:
class: 'App\EventListener\VichCleanListener'
parent: 'vich_uploader.listener.doctrine.base'
autowire: false
autoconfigure: false
public: false
4. 更改 Gedmo 扩展的优先级
如果以上还不够,现在出现了另一个问题:监听器的优先级。至少,我们需要确保 LoggableListener
在上传/清理监听器之后被触发。如果您使用了其他 Gedmo 扩展,您需要确保它们按您需要的顺序加载。VichUploaderExtension 设置的默认值 将 CleanListener
设置为 50
,将 UploadListener
设置为 0
。您可以在 StofDoctrineExtensionsExtension
中查看 Gedmo 监听器的默认值。
对于我来说,我有一个属性名称器依赖于可缩放字段,因此我希望确保
SluggableListener
在
UploadListener
之前被调用。我还使用
softdeleteable
并希望软删除被记录为 "remove",因此我希望确保在
SoftDeleteableListener
之前注册
LoggableListener
。您可以通过覆盖配置中的服务来更改这些优先级。
services:
stof_doctrine_extensions.listener.sluggable:
class: '%stof_doctrine_extensions.listener.sluggable.class%'
autowire: false
autoconfigure: false
public: false
calls:
- { method: 'setAnnotationReader', arguments: ['@annotation_reader'] }
tags:
- { name: 'doctrine.event_subscriber', connection: 'default', priority: 5 }
stof_doctrine_extensions.listener.loggable:
class: '%stof_doctrine_extensions.listener.loggable.class%'
autowire: false
autoconfigure: false
public: false
calls:
- { method: 'setAnnotationReader', arguments: ['@annotation_reader'] }
tags:
- { name: 'doctrine.event_subscriber', connection: 'default', priority: -1 }
stof_doctrine_extensions.listener.softdeleteable:
class: '%stof_doctrine_extensions.listener.softdeleteable.class%'
autowire: false
autoconfigure: false
public: false
calls:
- { method: 'setAnnotationReader', arguments: ['@annotation_reader'] }
tags:
- { name: 'doctrine.event_subscriber', connection: 'default', priority: -2 }
或者,您可以创建一个编译器传递,仅更改这些服务的doctrine.event_subscriber
标签的优先级。
namespace App\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class DoctrineExtensionsCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$listenerPriorities = [
'sluggable' => 5,
'loggable' => -1,
'softdeleteable' => -2,
];
foreach ($listenerPriorities as $ext => $priority) {
$id = sprintf('stof_doctrine_extensions.listener.%s', $ext);
if (!$container->hasDefinition($id)) {
continue;
}
$definition = $container->getDefinition($id);
$tags = $definition->getTag('doctrine.event_subscriber');
$definition->clearTag('doctrine.event_subscriber');
foreach ($tags as $tag) {
$tag['priority'] = $priority;
$definition->addTag('doctrine.event_subscriber', $tag);
}
}
}
}
如果您选择这种方法,请确保以更高的优先级(高于0)注册编译器传递,以确保它在
RegisterEventListenersAndSubscribersPass
之前运行。
use App\DependencyInjection\Compiler\DoctrineExtensionsCompilerPass;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
protected function build(ContainerBuilder $container)
{
$container->addCompilerPass(new DoctrineExtensionsCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 5);
}
现在,只需确保清除缓存。