The Database: Eloquent ORM
Website Rebuild with Laravel
Chapter 6 · The Database: Eloquent ORM
The migration and model exist since Chapter 2 — this chapter is about actually living with Eloquent day to day: running the migration for real, Laravel's own interactive shell, and the exact same N+1 query trap Django Rebuild 6 caught, already sitting in this course's own Chapter 4 breadcrumb code too.
Running the Migration & php artisan tinker
tinker is Laravel's own interactive REPL — the direct equivalent of Django's own manage.py shell. Page::create([...]) mass-assigns through Chapter 2's own $fillable list — the same protection is doing real work on every call here, not just a one-time setup detail.
Eloquent's Query Builder: Chained, Then a Real Collection
Chained methods like ->where() and ->orderBy() build up the query without running it — ->get() (or ->first(), ->count()) is what actually executes it. What comes back from a terminal method is a Collection — a real, already-fetched, enhanced array-like object with its own useful methods (->map(), ->filter(), ->pluck()), not something you can call ->where() on again to keep refining the same database query.
A Real, Live N+1 Example — Again
$node->parent access that hasn't already been loaded triggers its own fresh query — a genuine N+1 pattern, exactly like the one Django Rebuild 6 caught in its own identical breadcrumb code. And exactly the same reasoning applies here too: breadcrumb depth is bounded (this site never goes more than a handful of levels deep), so this stays a small, currently acceptable cost — worth naming honestly rather than pretending the code is already perfectly optimized, not worth an urgent fix on its own.
->with(): Eager Loading, But Not a JOIN
select_related('parent') fetches the related rows in one query, via a genuine SQL JOIN. Eloquent's with('parent') solves the identical N+1 problem, but through a genuinely different mechanism: it runs a second, separate query — a batched WHERE id IN (...) against every parent ID collected from the first query's results — then stitches the two result sets together in PHP memory. Both fully eliminate the N+1 pattern; one does it in a single joined query, the other in two batched ones. Neither is "wrong" — they're just genuinely different SQL underneath an almost identical-looking call.
Hands-On Exercises
Explain what Page::where(...)->get() actually returns, and why you can't chain another ->where() onto the result afterward the way you might expect from a "queryset" mental model.
Explain why the Controller's own breadcrumb loop from Chapter 4 has the identical N+1 pattern Django Rebuild 6 caught in its own breadcrumb code, and why this chapter still treats it as acceptable for now.
📄 View solutionExplain the real mechanical difference between Django's select_related (one query, a JOIN) and Eloquent's with() (two queries, a batched WHERE IN). Both solve N+1 — how do they actually differ underneath?
Chapter 6 Quick Reference
php artisan tinker— Laravel's own interactive shell, the direct equivalent of Django'smanage.py shell- Eloquent's query builder — chained methods build the query lazily; a terminal method (
->get(),->first()) executes it immediately and returns a real, already-fetched Collection - A live N+1 example, again — Chapter 4's own breadcrumb loop, honestly acceptable for now given its bounded depth
with('parent')— Eloquent's eager loading; solves N+1 in exactly two queries total- A real mechanism difference: Django's
select_relateduses one query with a JOIN; Eloquent'swith()uses two queries with a batchedWHERE IN, not a join - Next chapter: Rendering Content & the Kanji Edge Case