1. Resource Controllers
A resource controller groups the seven conventional actions for managing one kind of resource — list, show a form to create, store, show one, show a form to edit, update, delete — under standardized method names:
php artisan make:controller TaskController --resource
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class TaskController extends Controller
{
public function index() { /* GET /tasks -- list */ }
public function create() { /* GET /tasks/create -- new-item form */ }
public function store(Request $request) { /* POST /tasks -- save new item */ }
public function show(int $id) { /* GET /tasks/{id} -- one item */ }
public function edit(int $id) { /* GET /tasks/{id}/edit -- edit form */ }
public function update(Request $request, int $id) { /* PUT/PATCH /tasks/{id} */ }
public function destroy(int $id) { /* DELETE /tasks/{id} */ }
}
One route registers all seven, wired to their conventional HTTP verb + URI:
<?php
use App\Http\Controllers\TaskController;
use Illuminate\Support\Facades\Route;
Route::resource('tasks', TaskController::class);
// Registers all 7 routes at once, all correctly named (tasks.index, tasks.store, ...)
Run php artisan route:list after adding this and you'll see exactly
which URI, verb and name Laravel assigned to each of the seven methods —
worth doing once so the convention isn't a black box.
2. The Request Object
Instead of reading $_POST/$_GET directly, Laravel injects
a Request object into your controller method — type-hint it and
Laravel supplies the current request automatically:
<?php
public function store(Request $request)
{
$title = $request->input('title'); // like $_POST['title'] ?? null
$title = $request->input('title', ''); // with a default
$isUrgent = $request->boolean('urgent'); // "1"/"true"/"on" -> real bool
$all = $request->only(['title', 'notes']); // just these keys, as an array
if ($request->hasFile('attachment')) {
$path = $request->file('attachment')->store('attachments'); // handles the move for you
}
// ...
}
$request->file('attachment')->store('attachments') replaces the manual
move_uploaded_file() dance from Week 5 — Laravel generates a safe
random filename and handles the move, storing relative to
storage/app/ by default.
3. Validation Rules
$request->validate() replaces the manual if-checks from Week 2's
contact form with declarative rules — invalid input automatically redirects back
with error messages, with zero manual branching:
<?php
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|string|max:200',
'due_date' => 'nullable|date|after:today',
'priority' => 'required|in:low,medium,high',
]);
Task::create($validated); // Week 11 covers Eloquent's create() properly
return redirect()->route('tasks.index')->with('success', 'Task created!');
}
If validation fails, validate() throws internally, and Laravel's
exception handling catches it, redirects back to the previous page, and flashes
both the errors and the old input into the session automatically — a Blade view
reads them with @error('title') and old('title'):
<input name="title" value="{{ old('title') }}">
@error('title')
<p class="error">{{ $message }}</p>
@enderror
This is exactly the "re-fill the form, show the error" pattern built by hand in Week 2 — Laravel automates the plumbing, but the underlying idea (and the reason it exists) is unchanged.
4. Form Requests
Once a controller has several validated actions, inline rule arrays get repetitive. A Form Request extracts validation (and authorization) into its own class:
php artisan make:request StoreTaskRequest
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreTaskRequest extends FormRequest
{
public function authorize(): bool
{
return true; // Week 13 replaces this with a real authorization check
}
public function rules(): array
{
return [
'title' => 'required|string|max:200',
'due_date' => 'nullable|date|after:today',
'priority' => 'required|in:low,medium,high',
];
}
}
<?php
use App\Http\Requests\StoreTaskRequest;
public function store(StoreTaskRequest $request)
{
// Validation already ran before this line executes -- $request->validated()
// returns only the fields that passed the rules() array above.
Task::create($request->validated());
return redirect()->route('tasks.index')->with('success', 'Task created!');
}
Type-hinting StoreTaskRequest instead of the base Request
is enough — Laravel runs its rules() (and, once authorization logic
exists, its authorize()) before the method body executes at all. The
controller method never even runs if validation fails.
5. Redirects & Flash Data
->with('success', '...') in the examples above flashes a one-time
session value — the same concept built by hand in Week 5, now a built-in helper:
@if (session('success'))
<div class="alert">{{ session('success') }}</div>
@endif
Reading session('success') once and letting it naturally clear on the
next request is exactly the manual flash() helper from Week 5, minus
the code you'd otherwise have to write and maintain yourself.
6. Hands-on Exercise
Convert Week 9's routes into a resource controller
Move last week's closures into a proper controller with real validation, still on hardcoded in-memory data (Eloquent arrives next week).
Requirements:
- Generate
TaskControllerwith--resource, and register it withRoute::resource('tasks', TaskController::class). - Move last week's hardcoded task array into a static property on the controller (temporary — Week 11 replaces this with a real database).
- Build a
StoreTaskRequestForm Request with rules fortitle(required, max 200 chars) andpriority(required, one oflow/medium/high). - Wire
create.blade.phpandstore()together so submitting an invalid form redirects back showing errors via@errorand re-fills fields viaold(). - On successful creation, redirect to
tasks.indexwith a flashed success message, displayed once at the top of the layout.
A static property is a deliberate, temporary stand-in for a real data store — it will not persist correctly across separate PHP-FPM/CLI-server processes in production. Treat it as a placeholder that Week 11's Eloquent models properly replace, not a pattern to keep.
7. Knowledge Check
Four quick questions. Expand each to check your answer.
Q1
What does Route::resource('tasks', TaskController::class) register in one line?
What does Route::resource('tasks', TaskController::class) register in one line?
All seven conventional RESTful routes — index, create, store, show, edit, update and destroy — each mapped to the correct HTTP verb, URI pattern and controller method, and each pre-named following the tasks.* convention (tasks.index, tasks.store, and so on).
Q2
What happens if $request->validate([...]) fails inside a controller method?
What happens if $request->validate([...]) fails inside a controller method?
It throws a ValidationException internally, which Laravel's exception handler catches automatically — the rest of the method never runs. For a normal web request, this redirects the user back to the previous page with the validation errors and their submitted input both flashed into the session, ready to be read with @error and old().
Q3
Why move validation rules into a Form Request class instead of leaving them inline in store()?
Why move validation rules into a Form Request class instead of leaving them inline in store()?
It separates validation (and eventually authorization) concerns from the controller's actual business logic, and the same rule set can be reused if another action needs identical validation. Just as importantly, it runs validation before the controller method body even executes, guaranteeing the method only ever runs with already-valid data — there's no way to accidentally skip the check.
Q4
Is ->with('success', 'Task created!') a new concept, or a Laravel-provided version of something built by hand earlier in this course?
Is ->with('success', 'Task created!') a new concept, or a Laravel-provided version of something built by hand earlier in this course?
It's the same flash-message concept implemented by hand with $_SESSION in Week 5 — a value stored in the session that's meant to be read exactly once, typically to show a confirmation after a redirect. Laravel's session('success') read helper handles the "read once, then clear" mechanics automatically, the same job the hand-written flash() function did back then.