php - Symfony vich uploader 和学说可记录扩展问题?

标签 php symfony vichuploaderbundle doctrine-extensions

我正在使用这两个库创建一个实体,该实体使用 vich/uploader-bundle 来创建具有图片的实体我正在使用 loggable 记录实体更改历史记录stof/doctrine-extensions-bundle 提供的学说扩展它提供了 atlantic18/doctrineextensions 的扩展名.

所以这就是问题所在:我有一个具有 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;

嵌入式实体类 App\Entity\Embedded\VersionedFile 具有所有需要的注释,以便使用可记录的学说扩展正确地进行版本控制。

// 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 方法。此监听器检查更改并安排要插入的日志条目。

问题是 VichUploader 的上传监听器 (Vich\UploaderBundle\EventListener\Doctrine\UploadListener) 在可记录监听器之后被调用,然后文件被上传,这改变了属性名称、大小等。关于名称、大小等的计算更改在 LoggableListener` 中不可用,因为它首先被调用,所以它不知道应该插入它们。

我是不是遗漏了某些配置,还是做错了什么。这个想法是记录对图片所做的更改。目前在数据库中,日志条目仅包含 $pictureUpdatedAt 字段。

我调试了这个问题,我只能看到顺序,并且在 LoggableListener 中,方法 getObjectChangeSetData 仅返回 $pictureUpdatedAt 字段那已经改变了。我不认为这与嵌入式实体有任何共同之处,因为我认为听众的调用顺序是问题所在。我的第一个想法是改变监听器的优先级,但即使我这样做了,调用的顺序也没有改变,主要是因为当 onFlush 被调用时,它触发了 preUpdate触发上传程序包的 UploadListener 的方法。

最佳答案

你是对的,问题的根源是UploadListenerLoggableListener 时监听 prePersistpreUpdate听取 onFlush。由于 onFlushpreUpdate 之前触发,因此永远不会记录文件更改。这可以通过几个步骤解决。

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 来处理新的上传,则尤其如此。 prePersistonFlush 之前触发,因此这会使新的上传设置比以前晚。

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 VichUploaderExtensionCleanListener 设置为 50,将 UploadListener 设置为 0。你可以看到 Gedmo Listener defaultsStofDoctrineExtensionsExtension 中。

对我来说,我有一个依赖于 sluggable 字段的属性命名器,所以我想确保 SluggableListenerUploadListener 之前调用。我还使用 softdeleteable 并希望软删除记录为“删除”,因此我想确保 LoggableListenerSoftDeleteableListener 之前注册.您可以通过覆盖配置中的服务来更改这些优先级。

# 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);
}

现在,只需确保您的缓存已清除。

关于php - Symfony vich uploader 和学说可记录扩展问题?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56808473/

相关文章:

php - Couchbase PHP SDK : How to detect couchbase connection failure?

php - 如何在 Controller 中正确执行 symfony2 中的 If 语句

rest - symfony 形式 + fos 休息

symfony4 - 设置占位符并重命名 'browse' 按钮

angularjs - 如何使用 angular + symfony + vichuploaderBundle 上传图片

php - 对如何从返回一组特定值的查询中获取另一行感到困惑

php - 确定数字在哪个范围内的最有效方法?

PHP 将图像插入数据库

forms - 有没有办法在 symfony2 中围绕输入包装标签?

symfony - 通过 VichUploaderBundle 上传文件时出现 ContextErrorException