Dynamic Content & Forms

Website Rebuild with Laravel

Chapter 8 · Dynamic Content & Forms

Django Rebuild 8 answered Next.js's implicit Server Actions with three explicit pieces: a view function, a ModelForm, a URL. Laravel's own answer keeps that same explicit spirit, but adds a genuine extra layer neither sibling needed — a real Controller class, already established since Chapter 3. This chapter builds the same deliberately-incomplete updateTitle mutation both sibling courses built, closed the same way, one chapter later, in Chapter 9.

The Route: Automatic Lookup via Model Binding

// routes/web.php Route::post('/admin/pages/{page}/title', [PageController::class, 'updateTitle']) ->name('pages.updateTitle');

{page} in the route triggers Laravel's own route model binding — as long as the Controller method's own parameter is type-hinted Page $page, Laravel automatically looks the row up before the method body even runs. Neither Django's function-based views nor a plain Next.js Server Action have quite this automatic an equivalent — Django's own view still needs an explicit get_object_or_404 call inside the function body.

The Form Request: Validation and Authorization, Structurally Separated

php artisan make:request UpdatePageTitleRequest // app/Http/Requests/UpdatePageTitleRequest.php class UpdatePageTitleRequest extends FormRequest { public function authorize() { return true; // deliberately no auth check yet — closed in Chapter 9 } public function rules() { return [ 'title' => 'required|string|max:200', ]; } }
The gap is literally visible in the code, not just absent
Laravel's own FormRequest class has a dedicated authorize() method — a structural separation between "is this data valid" (rules()) and "is this user actually allowed to do this" (authorize()) that neither Django's ModelForm nor a bare Next.js Server Action enforces quite so explicitly. Returning true here isn't just missing a check the way the sibling courses' own gaps were — it's a real, visible line of code that will change to a genuine condition in Chapter 9. The deliberate incompleteness is structurally on the page, not just an absence someone has to notice.

The Controller Method

public function updateTitle(UpdatePageTitleRequest $request, Page $page) { $page->update($request->validated()); return redirect()->route('page.show', ['path' => $page->full_path]); }

Type-hinting UpdatePageTitleRequest is what actually triggers validation — Laravel runs rules() automatically, before this method body even starts, and redirects back with errors on failure without a single line of validation-checking code written here. $request->validated() returns only the fields that passed — safe to hand straight to ->update(), the same protection Chapter 2's own $fillable already provides against anything unexpected slipping through.

@csrf: The Same Requirement, the Same Gotcha

<form method="POST" action="{{ route('pages.updateTitle', $page) }}"> @csrf <input type="text" name="title" value="{{ $page->title }}"> <button type="submit">Save</button> </form>

@csrf is Blade's own direct equivalent of Django's {% csrf_token %} — Laravel's VerifyCsrfToken middleware rejects every POST by default unless the matching hidden token is present, and forgetting @csrf produces the identical "form looks correct, submits fine, still fails" confusion Django Rebuild 8 already warned about.

Three Explicit Answers, Compared

DjangoLaravel
Pieces requiredA view function, a ModelForm, a URLA Controller method, a FormRequest, a route
Object lookupExplicit get_object_or_404 inside the viewAutomatic, via route model binding
Validation vs. authorizationNot structurally separatedTwo distinct methods — rules() and authorize()

Hands-On Exercises

Exercise 1

Explain what route model binding does in Route::post('/admin/pages/{page}/title', ...), and why the Controller method never needs to call Page::findOrFail() itself.

📄 View solution
Exercise 2

Explain why authorize() currently returns true, and why this chapter treats that as a visible, structural placeholder rather than simply describing "no check at all" the way the sibling courses did.

📄 View solution
Exercise 3

Explain what $request->validated() actually returns, and why it's safe to pass directly into $page->update(...).

📄 View solution

Chapter 8 Quick Reference

  • Route model binding{page} plus a type-hinted Page $page parameter auto-resolves the row, no manual lookup needed
  • FormRequest — a dedicated class with rules() for validation and authorize() for permission, structurally separated
  • authorize() { return true; } — the deliberate no-auth-check-yet gap, made visible in code rather than just absent, closed in Chapter 9
  • $request->validated() — only the fields that passed validation; safe to mass-update directly
  • @csrf — Blade's own direct equivalent of {% csrf_token %}; the same "why won't my form submit" trap if forgotten
  • Genuinely more structure than Django — a real Controller class layer, plus explicit validation/authorization separation Django's function-based views don't require
  • Next chapter: Admin Authentication