Вы правы, корень проблемы в UploadListener
прослушивает prePersist
и preUpdate
, пока LoggableListener
слушает onFlush
. Поскольку onFlush
запускается до preUpdate
, изменения файлов никогда не регистрируются. Это можно исправить в несколько шагов.
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 для обработки новых загрузок. prePersist
запускается до onFlush
, поэтому новые загрузки будут устанавливаться позже, чем раньше.
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, вам необходимо убедиться, что они загружены в том порядке, в котором они вам нужны. значения по умолчанию, установленные VichUploaderExtension, устанавливают CleanListener
до 50
и от UploadListener
до 0
. Вы можете увидеть прослушиватель Gedmo по умолчанию в StofDoctrineExtensionsExtension
.
Для меня у меня есть имя свойства, которое зависит от slugable поля, поэтому я хочу убедиться, что SluggableListener
вызывается до UploadListener
. Я также использую softdeleteable
и хочу, чтобы мягкое удаление регистрировалось как «удаление», поэтому я хочу убедиться, что LoggableListener
зарегистрировано до SoftDeleteableListener
. Вы можете изменить эти приоритеты, переопределив службы в вашей конфигурации.
# 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);
}
Теперь просто убедитесь, что ваш кеш очищен.
person
jeffhinshaw
schedule
29.08.2019