← Home ← Codex ← DEBT ← Engine
Browse by Category
+ added · updated 7d
← Back to glossary

Unit of Work

Code Quality Advanced
debt(d7/e5/b7/t5)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7). The detection_hints indicate automated detection is 'no', and while specialist tools like doctrine-profiler and laravel-debugbar can surface symptoms (N transactions, memory growth), they require deliberate profiling sessions rather than passive linting. The code patterns — flush() inside loops, missing rollbacks, memory growth in batch processing — are invisible in normal development and only surface under load or during careful review.

e5 Effort Remediation debt — work required to fix once spotted

Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix sounds simple (move flush() outside loop), but common_mistakes reveal layered issues: incorrect transaction wrapping, mixed aggregate roots, misunderstood identity map behavior. Correcting all misuses typically requires restructuring how domain operations are batched and how repositories interact with the EntityManager across multiple files, not a single-line swap.

b7 Burden Structural debt — long-term weight of choosing wrong

Closest to 'strong gravitational pull' (e7 mapped to b7). Unit of Work applies across web, cli, and queue-worker contexts and is tagged as architecture/ORM/database/patterns. Every domain operation, batch job, and repository method must be designed around how and when flush() is called and how transactions are scoped. This shapes every data-mutation flow in the codebase, imposing a persistent mental model requirement on all maintainers.

t5 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'notable trap (a documented gotcha most devs eventually learn)' (t5). The misconception field explicitly states developers believe Unit of Work is ORM-only, missing its broader applicability. Additionally, the common_mistakes show non-obvious behaviors: flush() inside loops causing N transactions, identity map stale state after failed flushes, and the same object returned from double-fetches. These are documented gotchas that experienced developers eventually learn but regularly catch newcomers.

About DEBT scoring →

Also Known As

UoW change tracker

TL;DR

A pattern that tracks all changes made to domain objects during a business transaction and coordinates writing them out as a single atomic database operation.

Explanation

Unit of Work, described by Martin Fowler, maintains lists of objects affected by a business transaction: new, dirty (modified), and clean. At the end of the transaction, it coordinates persisting all changes in the correct order — inserts before updates, respecting foreign key constraints, within a single database transaction. ORMs like Doctrine implement Unit of Work automatically. Without it, you either make individual database calls per change (missing atomicity) or build transaction management ad hoc.

Common Misconception

Unit of Work is only relevant when using an ORM — any code that batches multiple domain changes into one database transaction is implementing Unit of Work; ORMs just automate it.

Why It Matters

Without Unit of Work, multi-step domain operations either make partial updates on failure or require manually managing transactions — both are error-prone in large codebases.

Common Mistakes

  • Calling $em->flush() inside a loop — each flush is a database round trip; flush once after all changes.
  • Not calling rollback on exception — Doctrine's Unit of Work holds changes in memory; a failed flush leaves the identity map stale.
  • Mixing multiple aggregate roots in one Unit of Work transaction — each aggregate should commit independently.
  • Not understanding that Doctrine's identity map is part of Unit of Work — fetching the same entity twice returns the same object.

Code Examples

✗ Vulnerable
// Flush inside loop — N database round trips:
foreach ($products as $product) {
    $product->setPrice($product->getPrice() * 1.1);
    $em->flush(); // 1 DB write per product — should be once at the end
}
✓ Fixed
// Batch all changes, single flush:
foreach ($products as $product) {
    $product->setPrice($product->getPrice() * 1.1); // Marked dirty in UoW
}
$em->flush(); // One transaction, all products updated atomically

Added 15 Mar 2026
Edited 22 Mar 2026
Views 112
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings W 0 pings T 1 ping F 0 pings S 0 pings S 1 ping M 0 pings T 0 pings W 0 pings T 1 ping F 0 pings S 0 pings S 1 ping M 1 ping T 1 ping W 1 ping T 0 pings F 0 pings S 2 pings S 1 ping M 0 pings T 0 pings W 1 ping T 0 pings F 0 pings S 0 pings S 3 pings M 0 pings T 1 ping W 0 pings T
No pings yet today
No pings yesterday
Amazonbot 10 Google 9 PetalBot 9 Ahrefs 8 SEMrush 8 Unknown AI 5 Scrapy 5 Perplexity 4 Bing 4 ChatGPT 2 Applebot 2 Meta AI 1 Twitter/X 1 Brave Search 1
crawler 65 crawler_json 3 pre-tracking 1
DEV INTEL Tools & Severity
🟡 Medium ⚙ Fix effort: Medium
⚡ Quick Fix
Doctrine's EntityManager IS a Unit of Work — call persist() to track objects, flush() to commit all changes in one transaction; never call flush() in a loop
📦 Applies To
any web cli queue-worker doctrine
🔗 Prerequisites
🔍 Detection Hints
flush() called inside loop causing N transactions; no transaction wrapping multiple operations; EntityManager not cleared in batch processing causing memory growth
Auto-detectable: ✗ No doctrine-profiler laravel-debugbar
⚠ Related Problems
🤖 AI Agent
Confidence: Low False Positives: High ✗ Manual fix Fix: High Context: File Tests: Update


✓ schema.org compliant