2

I'm looking for a way to return the UnitOfWork to a clean state without having the clear the whole thing. I still want to have access to the entities I've loaded. This is the base of what I have, but I'm not sure if this is going to break doctrine in weird ways.

$uow = $em->getUnitOfWork();
$uow->computeChangeSets();//Can this be called once here, or should it be called in the inside loop so we get fresh information after a potential cascading refresh or persist?
foreach ($uow->getIdentityMap() as $className => $entities) {
    foreach ($entities as $oid => $entity) {
        if ($uow->isEntityScheduled($entity)) {
            if ($uow->isScheduledForInsert($entity)) {
                $uow->scheduleForDelete($entity);//Removes from the insert list without cascading delete or calling removal events
            } elseif ($uow->isScheduledForUpdate($entity)) {
                $em->refresh($entity);
            } elseif ($uow->isScheduledForDelete($entity)) {
                $em->persist($entity);
                $em->refresh($entity);//Should I re-call computeChangeSets() here so I can check if the entity is now scheduled for update?
            }
        }
    }
}

For context: I am running a batch operation where I persist any needed changes after each record is processed. If one record fails due to a bug, that failure is handled, but it can break the rest of the batch with this type of exception:

A new entity was found through the relationship FOO#bar that was not configured to cascade persist operations for entity: otherFooBar .

Since I don't need to save any changes from processing a record if it fails, I'd like to simply clean up all changes and keep the object graph intact while continuing to process the batch.

I've read through the doctrine source and it seem like this should work correctly, but I am not intimately familiar with Doctrine's internals. Does anyone see or know of flaws/pitfalls with this approach or know of a better way to accomplish this?

EDIT: Clarifying the reason behind this question.

When an error occurs when processing a record, this can leave UoW / EntityManger in a position where it can no longer persist without being cleared. In my particular case the error left an existing changed entity with a link non-persisted entity where the relations was not configured to cascade persist, which causes all subsequent persists to fail. At this point, if I want to keep processing the batch and skip the failing record, I have two choices:

1) Clear the EntityManager and reload all the business logic entities from the database. but this means that the recovery function can't be generic and has to be customize to know what entities to reload and adjusted each time that changes.

2) Clean the EntityManager using the existing capabilities of UnitOfWork, this allows the code to be generic and does not require ongoing modification.

Omn
  • 2,982
  • 1
  • 26
  • 39
  • I've 'solved' this by `refresh()`ing the entities that were changed in the loop. Still error prone, because if you edit the code in the main loop, you possibly have to add/delete/update the cleanup code as well (i.e. the code, probably in the catch() clause, which does the `refresh()`). That coupling is not very evident. I'm amazed there isn't something of a rollback in Doctrine itself. – hbogert Jul 23 '18 at 07:32
  • @hbogert If you look at the sample code in the question, I do use refresh for entities that were scheduled for update or deletion. I don't believe that refresh works for new entities that are scheduled to be persisted. – Omn Jul 24 '18 at 17:38

2 Answers2

0

The problem with what you're doing in your example code is that you're using methods marked internal. Those aren't really intended for use by 'userland' code. If you want to do this safely, your best bet is really to just clear the EntityManager and start over.

That said, what you're doing looks like it should work. You don't need to call scheduleForDelete. Just call detach on the entity scheduled for insert, and it will be removed from the UnitOfWork.

  • 1
    The problem with clearing the entity manager is that I am trying to recover from an error. There are entities that are loaded when the error occurs that the code will still expect to exist and to not be detached from the EntityManager. Everything that I read about 'computeChangeSets' indicated that the calling it yourself will break persist behavior. Since I am trying to prevent any of the new items in the graph from persisting, that is not a problem. http://stackoverflow.com/a/16554133/1153227 – Omn Dec 16 '15 at 00:39
  • I don't really understand your use case. Are you trying to handle errors that occur during Doctrine's persist cycle, or are you handling errors from elsewhere in your app? – Bill Schaller Dec 16 '15 at 19:05
  • I'm handling a non-doctrine related error that puts the EntityManager in a state that causes an error for all future persists until it is cleared. I've edited my question with further clarification for why I'm solving the problem this way. – Omn Dec 18 '15 at 08:54
-1

when you detach on entity just detach that entity but maybe objec manager contain more managed object that will be persist to db on flush so you should use

 /**
     * Clears the ObjectManager. All objects that are currently managed
     * by this ObjectManager become detached.
     *
     * @param string|null $objectName if given, only objects of this type will get detached.
     *
     * @return void
     */
$this->manager->clear();
Ghazaleh Javaheri
  • 1,829
  • 19
  • 25
  • I specifically stated that I do NOT want to detach all managed objects, I simply want to completely reset the unit of work. – Omn Jan 16 '20 at 15:46