【问题标题】:Symfony vich uploader and doctrine loggable extension problem?Symfony vich 上传器和学说可记录扩展问题?
【发布时间】:2019-06-28 14:08:04
【问题描述】:

我正在使用这两个库创建一个实体,该实体使用vich/uploader-bundle 创建具有图片的实体,并且我正在使用stof/doctrine-extensions-bundle 提供的loggable 教义扩展记录实体更改历史记录,该扩展提供atlantic18/doctrineextensions 的扩展。

所以问题来了:我有一个具有 Vich 可上传图片字段的实体,并且它使用了带有注释的教义的 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 方法。此侦听器检查更改并安排要插入的日志条目。

问题是 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 theLoggableListener` 因为它被首先调用,所以它不知道应该插入它们。

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

我调试了问题,我看到的只是顺序,在LoggableListener 中,getObjectChangeSetData 方法只返回已更改的$pictureUpdatedAt 字段。我认为这与嵌入式实体没有共同点,因为我认为听众的调用顺序是问题所在。我的第一个想法是更改侦听器的优先级,但即使我这样做了,调用的顺序也不会改变,主要是因为当调用onFlush 时,它会触发preUpdate 方法,该方法会触发上传者的UploadListener捆绑。

【问题讨论】:

    标签: php symfony vichuploaderbundle doctrine-extensions


    【解决方案1】:

    你是对的,问题的根源是UploadListener 监听prePersistpreUpdateLoggableListener 监听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。您可以在StofDoctrineExtensionsExtension 中看到Gedmo Listener defaults

    对我来说,我有一个依赖于 sluggable 字段的属性命名器,所以我想确保在 UploadListener 之前调用 SluggableListener。我还使用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);
    }
    

    现在,只需确保清除缓存即可。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-28
      • 1970-01-01
      • 2013-04-01
      • 1970-01-01
      • 2012-06-24
      相关资源
      最近更新 更多