Symfony Vich上载程序和原则可记录扩展名问题?

时间:2019-06-28 14:08:04

标签: php symfony vichuploaderbundle doctrine-extensions

我正在使用这两个库使用vich/uploader-bundle创建一个具有图片的实体,并使用loggable提供的stof/doctrine-extensions-bundle学说扩展来记录实体更改历史,该扩展提供了扩展来自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方法。该侦听器检查更改并安排要插入的日志条目。

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

>

我缺少某些配置还是做错了什么?这个想法是记录对图片所做的更改。现在,在数据库中,日志条目仅由$pictureUpdatedAt字段组成。

我调试了问题,仅能看到顺序,并且在LoggableListener中,方法getObjectChangeSetData仅返回已更改的$pictureUpdatedAt字段。我认为这与嵌入式实体没有共同点,因为我认为侦听器的调用顺序是问题所在。我的第一个想法是更改侦听器的优先级,但是即使我这样做,调用的顺序也没有改变,主要是因为在调用onFlush时,它正在触发preUpdate方法,该方法将触发{{ 1}}。

1 个答案:

答案 0 :(得分:2)

您是正确的,问题的根源是UploadListener监听prePersistpreUpdate,而LoggableListener监听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

对我来说,我有一个依赖于缓慢字段的属性命名器,因此我想确保在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);
}

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