How can I tell if the current transaction will cha

2019-02-19 18:04发布

I'm using Doctrine to save user data and I want to have a last modification field. Here is the pseudo-code for how I would like to save the form once the user presses Save:

  • start transaction
  • do a lot of things, possibly querying the database, possibly not
  • if anything will be changed by this transaction
    • modify a last updated field
  • commit transaction

The problematic part is if anything will be changed by this transaction. Can Doctrine give me such information?

How can I tell if entities have changed in the current transaction?

edit

Just to clear things up, I'm trying to modify a field called lastUpdated in an entity called User if any entity (including but not limited to User) will be changed once the currect transaction is commited. In other words, if I start a transaction and modify the field called nbCars of an entity called Garage, I wish to update the lastUpdated field of the User entity even though that entity hasn't been modified.

4条回答
够拽才男人
2楼-- · 2019-02-19 18:14

Sorry for giving you the wrong answer at first, this should guide you in the right direction (note that it's not perfect).

You'll need to implement two events. One which listens to the OnFlush event, and acts like this:

// This should listen to OnFlush events
public function updateLastModifiedTime(OnFlushEventArgs $event) {
    $entity = $event->getEntity();
    $entityManager = $event->getEntityManager();
    $unitOfWork = $entityManager->getUnitOfWork();

    if (count($unitOfWork->getScheduledEntityInsertions()) > 0 || count($unitOfWork->getScheduledEntityUpdates()) > 0) {
        // update the user here
        $this->user->setLastModifiedDate(new \DateTime());
    }
}

We need to wait for the OnFlush event, because this is the only opportunity for us to get access to all of the work that is going to be done. Note, I didn't include it above, but there is also $unitOfWork->getScheduledEntityDeletions() as well, if you want to track that.

Next, you need another final event listener which listens to the PostFlush event, and looks like this:

// This should listen to PostFlush events
public function writeLastUserUpdate(PostFlushEventArgs $event) {
    $entityManager = $event->getEntityManager();
    $entityManager->persist($this->user);
    $entityManager->flush($this->user);
}

Once the transaction has been started, it's too late, unfortunately, to get doctrine to save another entity. Because of that, we can make the update to the field of the User object in the OnFlush handler, but we can't actually save it there. (You can probably find a way to do this, but it's not supported by Doctrine and would have to use some protected APIs of the UnitOfWork).

Once the transaction completes, however, you can immediately execute another quick transaction to update the datetime on the user. Yes, this does have the unfortunate side-effect of not executing in a single transaction.

查看更多
时光不老,我们不散
3楼-- · 2019-02-19 18:20

This is a necessary reply that aims at correcting what @ColinMorelli posted (since flushing within an lifecycle event listener is disallowed - yes, there's one location in the docs that says otherwise, but we'll get rid of that, so please don't do it!).

You can simply listen to onFlush with a listener like following:

use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Events;

class UpdateUserEventSubscriber implements EventSubscriber
{
    protected $user;

    public function __construct(User $user)
    {
        // assuming the user is managed here
        $this->user = $user;
    }

    public function onFlush(OnFlushEventArgs $args)
    {
        $em  = $args->getEntityManager();
        $uow = $em->getUnitOfWork();

        // before you ask, `(bool) array()` with empty array is `false`
        if (
            $uow->getScheduledEntityInsertions()
            || $uow->getScheduledEntityUpdates()
            || $uow->getScheduledEntityDeletions()
            || $uow->getScheduledCollectionUpdates()
            || $uow->getScheduledCollectionDeletions()
        ) {
            // update the user here
            $this->user->setLastModifiedDate(new DateTime());
            $uow->recomputeSingleEntityChangeSet(
                $em->getClassMetadata(get_class($this->user)), 
                $this->user
            );
        }
    }

    public function getSubscribedEvents()
    {
        return array(Events::onFlush);
    }
}

This will apply the change to the configured User object only if the UnitOfWork contains changes to be committed to the DB (an unit of work is actually what you could probably define as an application level state transaction).

You can register this subscriber with the ORM at any time by calling

$user         = $entityManager->find('User', 123);
$eventManager = $entityManager->getEventManager();
$subscriber   = new UpdateUserEventSubscriber($user);

$eventManager->addEventSubscriber($subscriber);
查看更多
等我变得足够好
4楼-- · 2019-02-19 18:27

My guess would have been, similarly to the other 2 answers, is to say whatever you want to do when there will or will not be changes, use event listeners.

But if you only want to know before the transaction starts, you can use Doctrine_Record::getModified() (link).

查看更多
做个烂人
5楼-- · 2019-02-19 18:28

@PreUpdate event won't be invoked if there's no change on the entity.

查看更多
登录 后发表回答