Home » Php » php – Doctrine2 how to deal with updating entities after rollback? ("The EntityManager is closed")

php – Doctrine2 how to deal with updating entities after rollback? ("The EntityManager is closed")

Posted by: admin July 12, 2020 Leave a comment

Questions:

I am trying to do some processing within a transaction and save information about potential failure, much like this:

$conn->beginTransaction();
try
{
    $report = $reportRepository->find($id);
    $user = $report->getUser();

    $specification = new Specification();
    $entityManager->persist($specification);

    throw new ProcessingWentWrongException();

    $entityManager->flush();
    $conn->commit();
}
catch(ProcessingWentWrongException $e)
{
    $conn->rollback();

    // Store error info:
    $report->setState('error');
    $entityManager->persist($report);
    $entityManager->flush(); // all hell breaks loose in here
}

This looks like a really common pattern, but Doctrine makes it really hard to do it:

  1. flush in the catch{} section will try to persist both the $report and $specification object which obviously is wrong, so I could clear the entityManager, but then…

  2. If I clear the entityManager, $report is no longer managed by it, so I need to call $em->merge($report) to make it managed again. Obviously $user will stay unmanaged so doctrine will either perform an insert or complain about persist cascade. So I can either merge() the whole graph (which sucks) or close the entityManager, but then…

  3. If I close the entityManager I can only re-retrieve the report instance via $repo->find($id); – but I don’t want to do that, it’s stupid.

Did I miss anything? Is there some other way to achieve the result above? I feel like Doctrine makes easy things hard.

How to&Answers:

Short answer

Use two entitymanagers. One for the potentially unsafe operations and one for logging/reporting on the other.

Long answer

Generally, you cannot make sure that errors don’t happen (some errors don’t happen before you flush to the database). And once they happen, the entitymanager is closed for business.

Here’s what I do (excerpts from config.yml):

doctrine:
    orm:
        default_entity_manager: 'default'
        entity_managers:
            default:
                mappings: { ... }
            logging:
                mappings: { ... }

For normal operations, I use the default entity manager, which requires no change to your code.

For meta-operations (like logging the progress or result of a batched import or something similar), I explicitly fetch the 'logging' manager and use it for creating/updating the logging/report entities (and only for those).

Answer:

In this particular example you are adding specification to Report. So can you this this?

$entityManager->clear("Your\Bundle\Entity\Specification");

and then do as you proposed:

// Store error info:
$report->setState('error');
$entityManager->persist($report);
$entityManager->flush(); // all hell breaks loose in here

Also, I think doing persist on object with assigned ID is invalid. ( $report object in catch branch)