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

doctrine, doctrine-orm, php, symfony

Solution

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).

Problem

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: `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... 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... 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.

Original source