Controllers
๐ฎ Controllers
routes/web.php from the last chapter work fine for a one-line example โ real request-handling logic belongs in a controller, Laravel's direct equivalent of Django's views.py. This chapter covers generating controllers, resource controllers matching last chapter's Route::resource(), and route model binding โ a genuinely distinctive Laravel convenience.
Generating a Controller
php artisan make:controller BookController # Controller created successfully. # app/Http/Controllers/BookController.php
// app/Http/Controllers/BookController.php namespace App\Http\Controllers; use Illuminate\Http\Request; class BookController extends Controller { public function index() { return 'All books'; } }
// routes/web.php โ wire the controller into a route Route::get('/books', [BookController::class, 'index']);
Controller Methods vs Django Views
Django Function-Based View vs Laravel Controller Method
Django
def book_detail(request, book_id): query = request.GET.get("q", "") return HttpResponse(...)
request always arrives as the first positional argument.
Laravel
public function show(Request $request, string $id) { $query = $request->query('q', ''); return ...; }
Request $request is type-hinted โ Laravel's service container injects it automatically based on the type, not its position.
This type-hint-based injection is a real PHP/Laravel-specific idea worth noticing early: any parameter Laravel recognizes by its type (a Request, a model class, a custom service) gets resolved and passed in automatically โ order relative to route parameters doesn't matter the way Python's positional arguments require.
๐ค Returning Responses
public function index() { return view('books.index', ['books' => Book::all()]); // Blade โ Chapter 4 } public function apiIndex() { return response()->json(['books' => Book::all()]); } public function store(Request $request) { // ... save ... return redirect()->route('books.index'); // by named route, per Chapter 2 }
view()
Combines a Blade template + data array + response into one call โ the direct parallel to Django's render().
response()->json()
The equivalent of Django's JsonResponse โ wraps a PHP array as a proper JSON response with the correct content type.
Resource Controllers
--resource generates method stubs for exactly the seven actions Route::resource() expects โ the controller-side half of last chapter's route generation:
php artisan make:controller BookController --resource
class BookController extends Controller { public function index() { /* GET /books */ } public function create() { /* GET /books/create */ } public function store(Request $request) { /* POST /books */ } public function show(Book $book) { /* GET /books/{book} */ } public function edit(Book $book) { /* GET /books/{book}/edit */ } public function update(Request $request, Book $book) { /* PUT /books/{book} */ } public function destroy(Book $book) { /* DELETE /books/{book} */ } }
Wiring it up needs only one route line, exactly matching Chapter 2:
Route::resource('books', BookController::class);
๐ Route Model Binding
Notice show(Book $book) above, not show(string $id) โ this is Laravel's standout convenience. Type-hint a model class as a parameter whose name matches the route segment, and Laravel fetches the row for you automatically:
Manual Lookup vs Route Model Binding
Django: Manual Lookup
def book_detail(request, book_id): book = get_object_or_404(Book, pk=book_id) return render(request, "book_detail.html", {"book": book})
Laravel: Automatic Binding
public function show(Book $book) { return view('books.show', ['book' => $book]); } // $book is ALREADY the fetched model โ no findOrFail() call written by hand
Laravel matches the route parameter {book} to the type-hinted Book $book by name, runs Book::findOrFail($id) internally, and injects the actual model instance โ a missing ID produces an automatic 404, with zero lookup code in the controller at all.
๐ป Coding Challenges
Challenge 1: Generate and Wire a Controller
Run php artisan make:controller AuthorController, add an index() method returning a plain string, and write the routes/web.php line needed to actually reach it at /authors.
Goal: Practice the generate-then-wire-up sequence, including the step that's easy to forget.
Challenge 2: Write a Resource Controller
Generate a resource controller for a Product model with --resource, fill in index() and show(Product $product) to return simple placeholder responses, and wire it up with Route::resource().
Goal: Practice pairing a resource controller with its matching resource route from Chapter 2.
Challenge 3: Convert Manual Lookup to Route Model Binding
Given a controller method show(string $id) that manually calls Book::findOrFail($id), rewrite it to use route model binding instead โ show(Book $book) โ and explain what happens differently if the ID doesn't exist.
Goal: Practice recognizing when manual lookup code can be replaced by Laravel's automatic binding.
php artisan make:controller only creates the file โ nothing routes to it until you add a matching entry in routes/web.php. This is the same two-step trap already seen twice in the Django courses (INSTALLED_APPS, include()): the code exists on disk, but the framework doesn't know about it yet. Separately, route model binding's automatic 404 is usually exactly what you want โ but it means a Book $book parameter can never actually be null inside the method; if you need to handle "not found" yourself instead of letting Laravel's default 404 page render, look up the model manually with Book::find($id) instead of relying on binding.
๐ฏ What's Next
Controllers can now return a view โ the next chapter covers what's actually inside one: Blade Templates, Laravel's templating syntax, control structures, and template inheritance compared to the Django Template Language.