BentCoder
BentCoder

Reputation: 12740

Handling doctrine transactions on multiple entity managers

Both transactions should be rollback-ed if;

  1. $em1 fails but $em2 succeeds.
  2. $em1 succeeds but $em2 fails.

So, is my example below correct way of dealing with transactions when more than one EMs are involved? I've come up with it after reading Transactions and Concurrency documentation.

$em1->getConnection()->beginTransaction();
$em2->getConnection()->beginTransaction();

try {
     $em1->persist($object1);
     $em1->flush();
     $em1->getConnection()->commit();

     $em2->persist($object2);
     $em2->flush();
     $em2->getConnection()->commit();
} catch (Exception $e) {
     $em1->getConnection()->rollback();
     $em2->getConnection()->rollback();
}

The reason I'm trying to implement this because I'm getting ....resulted in a Doctrine\ORM\ORMException exception (The EntityManager is closed.) error somewhere along the line in the application. I can probably handle it with the method below but I think using transaction for the business logic above is better.

private function getNewEntityManager($em)
{
    if (!$em->isOpen()) {
        $em = $em->create($em->getConnection(), $em->getConfiguration());
    }

    return $em;
}

Upvotes: 5

Views: 6909

Answers (2)

jake stayman
jake stayman

Reputation: 1808

Your example code actually does work, which surprises me because Francesco Panina is (or should be) correct that $em1->getConnection()->commit()

will commit the first transaction and you will loose [sic] the privilege to rollback such transaction should an error arise from the second transaction.

However, something in the way that Doctrine handles transaction nesting levels means that you actually can still rollback the first transaction when an error arises from the second transaction.

Nonetheless, best practice would be to not depend on this behavior and instead put both commits at the very end of your try block, as so:

$em1->getConnection()->beginTransaction();
$em2->getConnection()->beginTransaction();

try {
    $em1->persist($object1);
    $em1->flush();

    $em2->persist($object2);
    $em2->flush();

    $em1->getConnection()->commit();
    $em2->getConnection()->commit();
} catch (Exception $e) {
    $em1->getConnection()->rollback();
    $em2->getConnection()->rollback();
    throw $e;
}

With this small change, your example does demonstrate the correct way to deal with transactions that span multiple entity managers.

Upvotes: 3

Francesco Panina
Francesco Panina

Reputation: 343

I'd like to point out a couple of things that may clear your mind on the matter:

I'm not aware of the process you use to create the second entity manager, keep in mind that 2 completely different entity manager will not share the same connection. Can you point out your use case for 2 different entity manager?

Consider that the operation:

$em1->getConnection()->commit();

will commit the first transaction and you will loose the privilege to rollback such transaction should an error arise from the second transaction.


 Doctrine\ORM\ORMException exception (The EntityManager is closed.)

It's typical when you try to operate any commit/flush operation after a DBAL (database related) exception has been thrown; in this case Doctrine default behaviour is to close the entity manager. And it is common practice to do so after any rollback:

$em1->getConnection()->rollback();
$em1->close();

Hope it helps, Regards.

Upvotes: 1

Related Questions