简体   繁体   English

Symfony vich uploader和doctrine loggable扩展问题?

[英]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监听prePersistonFlush Since onFlush is triggered before preUpdate , file changes are never logged. 由于onFlush之前触发preUpdate ,文件更改从未登录。 This can be fixed in a few steps. 这可以通过几个步骤来解决。

1. Create New UploadListener 1.创建新的UploadListener

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. 在这个例子中,我重用了原始的UploadListenerUploadListener操作。 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. prePersistonFlush之前被触发,因此这将使新的上传设置比以前更晚。

2. Create New CleanListener 2.创建新的CleanListener

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];
    }
}

3. Configure New Listeners 3.配置新的监听器

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

4. Change Gedmo Extension Priorities 4.更改Gedmo扩展优先级

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设置默认值设置CleanListener50UploadListener0 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM