我使用这两个库来创建一个实体,该实体使用vich/uploader-bundle
创建图片,并使用stof/doctrine-extensions-bundle
提供的loggable
原理扩展来记录实体更改历史,stof/doctrine-extensions-bundle
提供了来自atlantic18/doctrineextensions
的扩展。
所以这里有一个问题:我有一个实体,它有一个Vich可上传的图片字段,并且它正在使用picture的Gedmo loggable扩展和注释。
/**
* @var VersionedFile
*
* @ORM\Embedded(class="App\Entity\Embedded\VersionedFile")
*
* @Gedmo\Versioned()
*/
private $picture;
/**
* @var File
*
* @Vich\UploadableField(
* mapping="user_picture",
* fileNameProperty="picture.name",
* size="picture.size",
* mimeType="picture.mimeType",
* originalName="picture.originalName",
* dimensions="picture.dimensions
* )
*/
private $pictureFile;
/**
* @var DateTimeInterface
*
* @ORM\Column(type="datetime", nullable=true)
*
* @Gedmo\Versioned()
*/
private $pictureUpdatedAt;
嵌入的实体类App\Entity\Embedded\VersionedFile
具有所有需要的注释,以便使用loggable原理扩展正确地进行版本控制。
// Not the whole code but just to get the idea for property versioning
/**
* @ORM\Column(name="name", nullable=true)
*
* @Gedmo\Versioned()
*/
protected $name;
现在问题来了。当我上传文件并持久化实体时,会发生以下事情。实体管理器持久化实体,并调用Gedmo可记录侦听器(Gedmo\Loggable\LoggableListener
)的onFlush方法。此监听程序检查更改并计划插入日志条目。
问题是VichUploaders upload listener (
Vich\UploaderBundle\EventListener\Doctrine\UploadListener) is called after the loggable listener and then the file is uploaded which changes the properties name, size, etc. The computed changes about name, size, etc. are not available in the
LoggableListener`是先被调用的,所以它不知道它们应该被插入。
是我错过了一些配置,还是我做错了什么。这个想法是记录对图片所做的更改。现在,在数据库中,日志条目只包含$pictureUpdatedAt
字段。
我调试了这个问题,我能看到的只有order,而且在LoggableListener
中,getObjectChangeSetData
方法只返回已更改的$pictureUpdatedAt
字段。我不认为这与嵌入式实体有什么共同之处,因为我认为监听器的调用顺序是问题所在。我的第一个想法是更改监听器的优先级,但即使我这样做了,调用的顺序也不会更改,主要是因为当onFlush
被调用时,它会触发preUpdate
方法,该方法会触发上载程序包的UploadListener
。
发布于 2019-08-30 01:54:29
您是对的,问题的根源在于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
,则当我们更新file字段时,此侦听器将删除旧文件。由于它监听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扩展优先级
如果所有这些还不够,现在出现了您提出的另一个问题:监听器优先级。至少,我们需要确保在我们的upload/clean监听器之后触发LoggableListener
。如果你正在使用任何其他的Gedmo扩展,你需要确保它们按照你需要的顺序加载。defaults set by VichUploaderExtension将CleanListener
设置为50
,将UploadListener
设置为0
。您可以在StofDoctrineExtensionsExtension
中看到Gedmo Listener defaults。
对我来说,我有一个依赖于可延迟字段的属性名称,所以我想确保在UploadListener
之前调用SluggableListener
。我也使用softdeleteable
,并且希望软删除被记录为"remove",所以我想确保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);
}
现在,只需确保清除缓存即可。
https://stackoverflow.com/questions/56808473
复制相似问题