Reputation: 12740
I have a onFlush() event which works fine but what I need to do is to turn that into preFlush() or preUpdate() both acceptable. I did preFlush() but for some reason it doesn't do anything. Not even an error. What am I missing?
TEST: I placed exit
in preFlush()
to see if it is being called at all or not. Outcome is: 1 so foreach()
is never run! It is an empty array. I also tested preUpdate() and all the lines in that get runed but no data inserted.
public function preFlush(PreFlushEventArgs $args)
{
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
echo '1';
foreach ($uow->getScheduledEntityUpdates() as $entity) {
echo '2';
if ($entity instanceof User) {
echo '3';
}
}
exit;
}
I created them after reading the documentation.
service.yml
services:
entity.event_listener.user:
class: Site\FrontBundle\EventListener\Entity\UserListener
tags:
- { name: doctrine.event_listener, event: preUpdate }
- { name: doctrine.event_listener, event: onFlush }
- { name: doctrine.event_listener, event: preFlush }
Working onFlush() example:
class UserListener
{
public function onFlush(OnFlushEventArgs $args)
{
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityUpdates() as $entity) {
if ($entity instanceof User) {
$userLog = new UserLog();
$userLog->setDescription($entity->getId() . ' being updated.');
$em->persist($userLog);
// Instead of $em->flush() cos we're already in flush process
$userLogMetadata = $em->getClassMetadata(get_class($userLog));
$uow->computeChangeSet($userLogMetadata, $userLog);
}
}
}
}
Not working preFlush() example:
class UserListener
{
public function preFlush(PreFlushEventArgs $args)
{
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityUpdates() as $entity) {
if ($entity instanceof User) {
$userLog = new UserLog();
$userLog->setDescription($entity->getId() . ' being updated.');
$em->persist($userLog);
// Instead of $em->flush() cos we're already in flush process
$userLogMetadata = $em->getClassMetadata(get_class($userLog));
$uow->computeChangeSet($userLogMetadata, $userLog);
}
}
}
}
Not working preUpdate() example
class UserListener
{
public function preUpdate(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
if ($entity instanceof User) {
$userLog = new UserLog();
$userLog->setDescription($entity->getId() . ') been updated.');
$em = $args->getEntityManager();
$em->persist($userLog);
$userLogMetadata = $em->getClassMetadata(get_class($userLog));
$uow->computeChangeSet($userLogMetadata, $userLog);
}
}
}
Upvotes: 6
Views: 25293
Reputation: 417
Dear BentCoder:
I'm using Symfony 2.7 version. When using $em->flush in event listener like your post, this error was occur:
click-here-to-see-bug-description
And, this is my solution:
Service.yml
services:
app.listener:
class: AppBundle\EventListener\DoctrineListener
arguments: ["@service_container"]
tags:
- { name: doctrine.event_listener, event: preUpdate, method: preUpdate }
- { name: doctrine.event_listener, event: postUpdate, method: postUpdate }
Event Listener
namespace AppBundle\EventListener;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Doctrine\ORM\Event\LifecycleEventArgs;
use AppBundle\Entity;
/**
* Log activity on website
* Class DoctrineListener
* @package AppBundle\EventListener
*/
class DoctrineListener
{
/**
* @var ContainerInterface
*/
private $_container;
/**
* @var Array
*/
private $_activities;
/**
* DoctrineListener constructor.
* @param ContainerInterface $container
*/
public function __construct(ContainerInterface $container)
{
$this->_container = $container;
}
/**
* @param LifecycleEventArgs $args
*/
public function preUpdate(LifecycleEventArgs $args)
{
$entityManager = $args->getEntityManager();
$entity = $args->getEntity();
$activityEntity = new Entity\Activity();
$activityEntity->setAction(Entity\Activity::ACTION_EDIT);
$activityEntity->setActionAt(new \DateTime());
$activityEntity->setIpAddress($this->_container->get('request')->getClientIp());
switch (true) {
case $entity instanceof Entity\Goods:
$repository = $entityManager->getRepository('AppBundle:Goods');
$activityEntity->setType(Entity\Activity::TYPE_GOODS);
$message = 'User: <strong>%s</strong> sửa mẫu hàng hóa <strong>%s</strong>';
break;
default:
return;
}
if (isset($repository) && $args->getEntityChangeSet()) {
$user = $this->_container->get('security.context')->getToken()->getUser();
$recordBefore = clone $entity;
foreach ($args->getEntityChangeSet() as $key => $value) {
$method = 'set'.ucfirst($key);
$recordBefore->$method($value[0]);
}
$activityEntity->setFosUser($user);
$activityEntity->setRecordBefore(serialize($recordBefore));
$activityEntity->setRecordAfter(serialize($entity));
$activityEntity->setMessage(
sprintf(
$message,
$user->getUserProfile()->getFullName(),
(string) $recordBefore
)
);
$this->_activities[] = $activityEntity;
}
return;
}
/**
* @param LifecycleEventArgs $args
*/
public function postUpdate(LifecycleEventArgs $args)
{
if (sizeof($this->_activities)) {
$entityManager = $args->getEntityManager();
foreach ($this->_activities as $activity) {
$entityManager->persist($activity);
}
$entityManager->flush();
}
}
}
Hope this will help someone!
Upvotes: 1
Reputation: 12740
SOLUTION:
The trick is, persisting after preUpdate()
within postFlush()
event.
Note: Although this might not be the best solution, it answers the question however it could be done with an Event Subscriber or simple onFlush()
-> $uow->getScheduledEntityUpdates()
in an Event Listener.
Service.yml
services:
entity.event_listener.user_update:
class: Site\FrontBundle\EventListener\Entity\UserUpdateListener
tags:
- { name: doctrine.event_listener, event: preUpdate }
- { name: doctrine.event_listener, event: postFlush }
Event Listener
<?php
namespace Site\FrontBundle\EventListener\Entity;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PostFlushEventArgs;
use Site\FrontBundle\Entity\User;
use Site\FrontBundle\Entity\UserLog;
class UserUpdateListener
{
private $log = array();
public function preUpdate(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
// False check is compulsory otherwise duplication occurs
if (($entity instanceof User) === false) {
$userLog = new UserLog();
$userLog->setDescription($entity->getId() . ' being updated.');
$this->log[] = $userLog;
}
}
public function postFlush(PostFlushEventArgs $args)
{
if (! empty($this->log)) {
$em = $args->getEntityManager();
foreach ($this->log as $log) {
$em->persist($log);
}
$em->flush();
}
}
}
Upvotes: 13
Reputation: 1986
Reading the docs,
http://doctrine-orm.readthedocs.org/en/latest/reference/events.html#onflush
it doesn't mention that preFlush
has the infor about changes(I mean the entityManager)
if you look at Doctrine\ORM\UnitOfWork
you see that change sets are computed after preFlush
event so you should use onFlush
if you want to interact with changed entities
// Raise preFlush
if ($this->evm->hasListeners(Events::preFlush)) {
$this->evm->dispatchEvent(Events::preFlush, new PreFlushEventArgs($this->em));
}
// Compute changes done since last commit.
if ($entity === null) {
$this->computeChangeSets();
} elseif (is_object($entity)) {
$this->computeSingleEntityChangeSet($entity);
} elseif (is_array($entity)) {
foreach ($entity as $object) {
$this->computeSingleEntityChangeSet($object);
}
}
Upvotes: 2