[英]Symfony vich uploader and doctrine loggable extension problem?
I am using this two libraries to create an entity that has a picture using vich/uploader-bundle
and I am logging entity changes history using the loggable
doctrine extension provided from stof/doctrine-extensions-bundle
which provides the extension from atlantic18/doctrineextensions
. 我正在使用这两个库创建一个使用
vich/uploader-bundle
创建图片的实体,并使用stof/doctrine-extensions-bundle
提供的loggable
doctrine扩展来记录实体更改历史记录,该扩展来自atlantic18/doctrineextensions
。
So here is the problem: I have an entity that has a Vich uploadable picture field and it is using doctrine's Gedmo loggable extension with annotations. 所以这就是问题所在:我有一个具有Vich可上传图片字段的实体,它正在使用带有注释的doctrine的Gedmo可记录扩展。
/**
* @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;
The embedded entity class App\\Entity\\Embedded\\VersionedFile
has all the needed annotations in order to version properly using the loggable doctrine extension. 嵌入式实体类
App\\Entity\\Embedded\\VersionedFile
具有所有必需的注释,以便正确使用可记录的doctrine扩展版本。
// Not the whole code but just to get the idea for property versioning
/**
* @ORM\Column(name="name", nullable=true)
*
* @Gedmo\Versioned()
*/
protected $name;
And now the problem. 而现在的问题。 When I upload the file and persist the entity the following thing happens.
当我上传文件并持久保存实体时,会发生以下情况。 The entity manager persist the entity and the onFlush method of the Gedmo loggable listener (
Gedmo\\Loggable\\LoggableListener
) is called. 实体管理器持久保存实体,并调用Gedmo可记录侦听器(
Gedmo\\Loggable\\LoggableListener
)的onFlush方法。 This listeners checks the changes and schedules log entries to be inserted. 此侦听器检查更改并计划要插入的日志条目。
The problem is that the VichUploader s 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` becaues it is called first and so it doesn't know that they should be inserted. 问题是在可记录
s upload listener (
器) 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
VichUploader s 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 ) 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
等等因为它首先被调用,所以它不知道应该插入它们。
Am I missing some configuration or am I doing something wrong. 我错过了一些配置还是我做错了什么。 The idea is to log changes made to the picture.
我们的想法是记录对图片所做的更改。 For now in the database the log entries consist only of the
$pictureUpdatedAt
field. 目前,在数据库中,日志条目仅包含
$pictureUpdatedAt
字段。
I debugged the problem and all I can see is the order and that in LoggableListener
the method getObjectChangeSetData
is returning only the $pictureUpdatedAt
field that has changed. 我调试了问题,所有我能看到的是顺序,而在
LoggableListener
,方法getObjectChangeSetData
只返回已更改的$pictureUpdatedAt
字段。 I don't think this has something in common with the Embedded entity because I think the calling order of the listeners is the problem. 我认为这与嵌入式实体没有共同之处,因为我认为听众的调用顺序是问题所在。 The first idea I had was to change the listeners priority but even if I do that the order of the calling is not changed mainly because when
onFlush
is called it is triggering the preUpdate
method which triggers the UploadListener
of the uploader bundle. 我的第一个想法是更改侦听器的优先级,但即使我这样做,调用的顺序也不会改变,主要是因为调用
onFlush
时它会触发preUpdate
方法,该方法会触发上传器包的UploadListener
。
You are correct, the root of the problem is the UploadListener
listens to prePersist
and preUpdate
while the LoggableListener
listens to onFlush
. 你是对的,问题的根源是当
LoggableListener
监听preUpdate
时, UploadListener
监听prePersist
和onFlush
。 Since onFlush
is triggered before preUpdate
, file changes are never logged. 由于
onFlush
之前触发preUpdate
,文件更改从未登录。 This can be fixed in a few steps. 这可以通过几个步骤来解决。
First, you can write your own UploadListener to listen to onFlush
instead. 首先,您可以编写自己的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];
}
}
In this example, I reuse the original UploadListener
to make things easier. 在这个例子中,我重用了原始的
UploadListener
来UploadListener
操作。 Since we are listening to onFlush
, it is important we recompute the entity changeset after the file is uploaded which is why I used the "preUpdate" method for both scheduled updates and inserts. 由于我们正在监听
onFlush
,因此重要的是我们在上传文件后重新计算实体变更集,这就是我为预定更新和插入使用“preUpdate”方法的原因。
You do have to be careful when changing events like this. 在更改这样的事件时你必须要小心。 If you have another listener that expects the value of one of your file fields to be set (or unset), this may change the expected behavior.
如果您有另一个侦听器需要设置(或取消设置)某个文件字段的值,则可能会更改预期的行为。 This is especially true if you use the second foreach to handle new uploads.
如果您使用第二个foreach来处理新上传,则尤其如此。
prePersist
is triggered before onFlush
, so this would make new uploads get set later than before. prePersist
在onFlush
之前被触发,因此这将使新的上传设置比以前更晚。
Next, we now have to create a new CleanListener
. 接下来,我们现在必须创建一个新的
CleanListener
。 This listener deletes old files when we update the file field if delete_on_update
is set to true
. 如果
delete_on_update
设置为true
则在更新文件字段时,此侦听器将删除旧文件。 Since it listens to preUpdate
, we have to change it to onFlush
so old files are properly deleted. 由于它监听
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];
}
}
Now, we need to override the default listeners in our config with the ones we just wrote. 现在,我们需要使用我们刚写的那些覆盖配置中的默认侦听器。
# 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
If all that wasn't enough, now comes the other problem you brought up: listener priority. 如果所有这些还不够,那么现在就出现了你提出的另一个问题:听众优先。 At a minimum, we need to make sure
LoggableListener
is triggered after our upload/clean listeners. 至少,我们需要确保在上传/清除侦听器之后触发
LoggableListener
。 If you are using any of the other Gedmo extensions, you need to make sure they are loaded in the order you need them. 如果您使用任何其他Gedmo扩展,则需要确保按照您需要的顺序加载它们。 The defaults set by VichUploaderExtension set the
CleanListener
to 50
and the UploadListener
to 0
. 在通过VichUploaderExtension设置默认值设置
CleanListener
至50
和UploadListener
到0
。 You can see the Gedmo Listener defaults in StofDoctrineExtensionsExtension
. 你可以看到Gedmo监听器默认的
StofDoctrineExtensionsExtension
。
For me, I have a property namer that depends on a sluggable field, so I want to make sure SluggableListener
is called before the UploadListener
. 对我来说,我有一个依赖于可缓冲字段的属性名称,所以我想确保在
SluggableListener
之前调用UploadListener
。 I also use softdeleteable
and want soft deletes logged as "remove", so I want to make sure LoggableListener
is registered before SoftDeleteableListener
. 我也使用
softdeleteable
并希望软删除记录为“删除”,所以我想确保在SoftDeleteableListener
之前注册LoggableListener
。 You can change these priorities by overriding the services in your config. 您可以通过覆盖配置中的服务来更改这些优先级。
# 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 }
Alternatively, you could create a compiler pass to just change the priorities of the doctrine.event_subscriber
tags for these services. 或者,您可以创建一个编译器传递来仅更改这些服务的
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);
}
}
}
}
If you go this route, make sure to register the compiler pass with a higher priority (higher than 0) to ensure it is ran before RegisterEventListenersAndSubscribersPass
. 如果你走这条路,请确保以更高的优先级(高于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);
}
Now, just ensure your cache is cleared. 现在,只需确保清除缓存。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.