ORM — Object-Relational Mapper
Also Known As
TL;DR
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
Why It Matters
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
// 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)
}
// 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
}