你是对的,问题的根源是UploadListener 监听prePersist 和preUpdate 而LoggableListener 监听onFlush。由于onFlush 在preUpdate 之前触发,因此永远不会记录文件更改。只需几个步骤即可解决此问题。
1。创建新的 UploadListener
首先,您可以编写自己的 UploadListener 来收听onFlush。
// src/EventListener/VichUploadListener.php using Flex
// src/AppBundle/EventListener/VichUploadListener.php otherwise
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));
}
// Required if using property namer on sluggable field. Otherwise, you
// can also subscribe to "prePersist" and remove this foreach.
foreach ($uow->getScheduledEntityInsertions() as $entity) {
// We use "preUpdate" here so the changeset is recomputed.
$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 设置为true,则当我们更新文件字段时,此侦听器会删除旧文件。由于它监听preUpdate,我们必须将其更改为onFlush,以便正确删除旧文件。
// src/EventListener/VichCleanListener.php on Flex
// src/AppBundle/EventListener/VichCleanListener.php otherwise
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。配置新的监听器
现在,我们需要用刚刚编写的监听器覆盖配置中的默认监听器。
# config/services.yaml on Flex
# app/config/services.yml otherwise
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 扩展,则需要确保它们按您需要的顺序加载。 defaults set by VichUploaderExtension 将CleanListener 设置为50,将UploadListener 设置为0。您可以在StofDoctrineExtensionsExtension 中看到Gedmo Listener defaults。
对我来说,我有一个依赖于 sluggable 字段的属性命名器,所以我想确保在 UploadListener 之前调用 SluggableListener。我还使用softdeleteable 并希望将软删除记录为“删除”,因此我想确保LoggableListener 在SoftDeleteableListener 之前注册。您可以通过覆盖配置中的服务来更改这些优先级。
# config/services.yaml on Flex
# app/config/services.yml otherwise
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 标记的优先级。
// src/DependencyInjection/Compiler/DoctrineExtensionsCompilerPass.php on Flex
// src/AppBundle/DependencyInjection/Compiler/DoctrineExtensionsCompilerPass.php otherwise
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 之前运行。
// src/Kernel.php on Flex
// src/AppBundle/AppBundle.php otherwsie
// ...
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);
}
现在,只需确保清除缓存即可。