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

ORM — Object-Relational Mapper

Database PHP 7.0+ Beginner
debt(d7/e3/b7/t7)
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.tools field is empty/unspecified. N+1 query problems are silent in normal development — the app works correctly but issues hundreds of queries. Detection requires deliberate query logging (e.g. Laravel Debugbar, query log inspection) or noticing slow pages under load. No default linter or compiler catches lazy loading in loops; it takes runtime profiling or careful code review to spot.

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3). The quick_fix confirms the fix is a targeted pattern replacement: swap Post::all() with Post::with(['author', 'comments'])->get(). This is a small, localised change — typically one or a few lines per offending query — but requires identifying every affected call site, which may span several controllers or services within a component.

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

Closest to 'strong gravitational pull' (b7). ORM is the default data access layer for Laravel/Symfony (applies_to: web, cli) and shapes every database interaction across the codebase. Relationship loading patterns, query construction habits, and architectural decisions (raw SQL vs ORM) are pervasive. Every feature touching the database is influenced by ORM choices, and switching or significantly changing the ORM approach would require cross-cutting refactoring.

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

Closest to 'serious trap' (t7). The misconception field documents a two-level trap: developers first believe ORMs are inherently slow (wrong — the ORM is not the bottleneck), then fall into lazy loading in loops which genuinely causes N+1 queries. This contradicts developer intuition — code that looks clean and correct ($post->author->name in a loop) silently degrades performance. It contradicts how similar iteration patterns behave in non-ORM contexts where there's no hidden per-iteration DB call.

About DEBT scoring →

Also Known As

ORM Eloquent Doctrine Active Record Data Mapper object-relational mapping

TL;DR

A library that maps database rows to PHP objects and vice versa — handling SQL generation, relationships, and lazy loading, at the cost of hiding query behaviour that can cause N+1 problems if used carelessly.

Explanation

An ORM bridges the object-oriented PHP world and the relational database world. Instead of writing SQL, you work with PHP objects (Eloquent models, Doctrine entities) and the ORM generates appropriate SQL. Two main patterns: Active Record (Eloquent in Laravel) — the model is both the domain object and the database gateway; User::find(1) issues SELECT and returns a User object; Doctrine in Data Mapper mode — entities are plain PHP objects, a separate Repository class handles persistence. ORM advantages: no raw SQL boilerplate; database-agnostic code (switch MySQL to PostgreSQL by changing config); built-in relationships (hasMany, belongsTo) with lazy loading; migrations. ORM pitfalls: lazy loading causes N+1 queries (loading 100 posts then accessing post->author triggers 100 individual author queries); ORM-generated SQL is not always optimal; complex reporting queries are easier in raw SQL.

Common Misconception

ORMs are slower than raw SQL and should be avoided in performance-sensitive code. ORMs generate correct SQL, and properly used with eager loading and indexes, perform identically to hand-written SQL. The performance issue is not the ORM itself but lazy loading causing N+1 queries — fixable with with() in Eloquent or JOIN FETCH in Doctrine. Use raw SQL only for complex reporting queries the ORM cannot express cleanly.

Why It Matters

ORMs are the default data access layer in Laravel and Symfony — understanding how they work prevents the most common performance antipattern in PHP applications. An Eloquent query like $posts = Post::all(); foreach ($posts as $post) { echo $post->author->name; } issues 1 + N database queries (one for posts, one per author). With $posts = Post::with('author')->get() it issues 2 queries total. Understanding the difference between lazy and eager loading is the single most impactful ORM knowledge for PHP developers.

Common Mistakes

  • Using lazy loading in loops — always eager-load relationships with with() when you know you will access them.
  • Calling ->get() and then ->count() separately — use ->count() alone which generates SELECT COUNT(*), not SELECT * then PHP count().
  • Using Model::all() on large tables — always add ->limit() or pagination to prevent loading entire tables into memory.
  • Over-using ORMs for reporting queries with multiple JOINs and aggregations — raw SQL with DB::select() is often cleaner and more readable for complex analytics.

Code Examples

✗ Vulnerable
// N+1 — 1 query for posts + 1 per post for author
$posts = Post::all(); // SELECT * FROM posts
foreach ($posts as $post) {
    echo $post->author->name; // SELECT * FROM users WHERE id = ? (×N)
    echo $post->comments->count(); // SELECT * FROM comments WHERE... (×N)
}
✓ Fixed
// Eager loading — 3 queries total regardless of post count
$posts = Post::with(['author', 'comments'])->paginate(20);
// SELECT * FROM posts LIMIT 20
// SELECT * FROM users WHERE id IN (1,2,3...)
// SELECT * FROM comments WHERE post_id IN (1,2,3...)
foreach ($posts as $post) {
    echo $post->author->name;     // no query — already loaded
    echo $post->comments->count(); // no query — already loaded
}

Added 23 Mar 2026
Edited 12 Jun 2026
Views 174
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
1 ping T 2 pings W 2 pings T 0 pings F 1 ping S 0 pings S 1 ping M 0 pings T 0 pings W 1 ping T 0 pings F 2 pings S 1 ping S 5 pings M 1 ping T 1 ping W 0 pings T 1 ping F 1 ping S 1 ping S 0 pings M 0 pings T 0 pings W 0 pings T 1 ping F 1 ping S 2 pings S 1 ping M 1 ping T 0 pings W
No pings yet today
Perplexity 1
Google 93 Perplexity 30 Amazonbot 16 Scrapy 7 SEMrush 5 Ahrefs 4 Bing 3 Meta AI 2 Claude 2 ChatGPT 1 Majestic 1 PetalBot 1
crawler 161 crawler_json 4
DEV INTEL Tools & Severity
🟡 Medium ⚙ Fix effort: Low
⚡ Quick Fix
Replace lazy loading with eager loading: Post::with(['author', 'comments'])->get() instead of Post::all() — eliminates N+1 queries for those relationships
📦 Applies To
PHP 7.0+ web cli


✓ schema.org compliant