When should EntityManager::clear() run inside a Doctrine batch loop to stop memory growth without losing pending changes?
0 reputation · 14 Jul 2021, 01:47 UTC
0 reputation · 14 Jul 2021, 01:47 UTC
I am processing a large result set in a long-running PHP CLI job using Doctrine ORM. Memory usage climbs steadily as the loop iterates, which I understand is the UnitOfWork identity map retaining every managed entity. The documented mitigation is periodic flush() followed by EntityManager::clear(), but I am unsure how to apply it safely in my case.
My loop both updates existing entities and occasionally creates related ones, so some entities are still referenced after the batch boundary. I know clear() detaches everything managed, which means those entities would need to be re-fetched or merged, and that calling it before flush() can silently discard pending changes. I am also unsure whether selective clearing by entity class is available in my ORM 2.x version, since the signature changed across releases.
What ordering of flush() and clear() guarantees no unflushed changes are lost? How can I confirm with memory_get_usage(true) that the identity map is actually the source of the growth? And how do I verify after clearing that a specific entity is detached before deciding whether to re-fetch it?
30775 reputation · 14 Jul 2021, 09:35 UTC
In a Doctrine ORM batch loop, the safe ordering is always flush() before EntityManager::clear(). flush() synchronizes all pending UnitOfWork changes to the database; clear() then detaches every managed entity, releasing the identity map. Reversing this order discards any unflushed inserts or updates silently, so the pair must run in this sequence at each batch boundary.
If your loop both updates existing entities and occasionally creates related ones, finish all work on an entity before the batch boundary, or re-obtain it afterwards via $em->find(EntityClass::class, $id) (fresh managed copy) or $em->merge($entity) (ORM 2.x; deprecated in later releases) to reattach state.
memory_get_usage(true) at the start of each iteration.$em->getUnitOfWork()->getScheduledEntityNames() to count managed entities.clear(), the UnitOfWork identity map is the primary driver.clear(), run $em->contains($entity) — it must return false for a detached entity.UnitOfWork::getEntityState($entity) — it should return STATE_DETACHED.$em->find($id) when the entity is still needed.flush() then clear().flush() to persist any trailing partial batch.$query->toIterable() or iterate() over getResult() to avoid loading the entire result set upfront.If your installed Doctrine ORM minor version supports clear($className), you may use it to detach only entity types you specify. Otherwise, explicit detach($entity) and merge($entity) patterns are recommended. Let me know your minor version to confirm the safest clearing approach.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.