Controllers

A controller is just a plain PHP class whose job is to group related request-handling logic together. Instead of stuffing every route's logic directly into a closure inside routes/web.php, controllers keep that logic organized by resource — an ArticleController for everything related to articles, a UserController for everything related to users, and so on. This lesson covers generating controllers, the different shapes they can take, and how to get data from a controller into a view.

1. Generating a Controller

Controllers live in app/Http/Controllers/, and Artisan can scaffold a new, empty one for you:

php artisan make:controller ArticleController

This creates app/Http/Controllers/ArticleController.php with an empty class already extending Laravel's base Controller class, ready for you to add methods to.

2. A Basic Controller

Each public method on a controller is an action — a method meant to be matched to a route. Here's a minimal, hand-written example:

<?php

namespace App\Http\Controllers;

class ArticleController extends Controller
{
    public function index()
    {
        return "A list of all articles would go here";
    }
}

Wiring it up to a route looks exactly like the controller routing you already saw in earlier lessons:

// routes/web.php
Route::get('/articles', [ArticleController::class, 'index']);

3. Resource Controllers

Since Route::resource() (covered in the previous lesson) expects seven specific methods on a controller, Artisan can generate a controller with matching stub methods already in place, using the --resource flag:

php artisan make:controller ArticleController --resource

This scaffolds all seven CRUD methods — index, create, store, show, edit, update, and destroy — each with an empty body ready to fill in, mapped exactly to the resource route table from the previous lesson. It's the fastest way to start building a full CRUD resource.

4. Invokable (Single-Action) Controllers

Sometimes a controller genuinely only needs to do one thing — render a dashboard, handle a single webhook, and nothing else. For these cases, Laravel supports invokable controllers: a controller class with a single __invoke() method instead of several named actions. Generate one with the --invokable flag:

php artisan make:controller ShowDashboard --invokable
class ShowDashboard extends Controller
{
    public function __invoke()
    {
        return view('dashboard');
    }
}

Routing to it needs no method name at all — just the class itself, since PHP calls __invoke() automatically:

Route::get('/dashboard', ShowDashboard::class);

5. Passing Data to a View

The second argument to the view() helper is an array of data made available to the Blade template as variables:

return view('articles.index', ['articles' => $articles]);

When the array key matches an existing variable's name exactly, PHP's compact() helper is a common shorthand that saves repeating the name:

$articles = Article::all();
return view('articles.index', compact('articles'));
// identical result to ['articles' => $articles]

6. A Full Worked Example

Putting it together — an index() action that lists articles, and a show() action that uses the route model binding from the previous lesson to fetch a single one:

<?php

namespace App\Http\Controllers;

use App\Models\Article;

class ArticleController extends Controller
{
    public function index()
    {
        $articles = Article::latest()->paginate(10);

        return view('articles.index', compact('articles'));
    }

    public function show(Article $article)
    {
        // $article is already resolved from the {article} route parameter
        return view('articles.show', compact('article'));
    }
}

7. Keeping Controllers Thin

It's tempting, especially early on, to let a controller action grow to include validation, complex querying, business rules, and formatting all in one place. As an application grows, this makes controllers hard to test and hard to reuse. A widely followed best practice is keeping controllers thin: their job is to receive a request, delegate the real work elsewhere (to Eloquent models, dedicated service classes, or form request classes), and return a response — not to contain the business logic itself. This theme comes up repeatedly through the rest of this track and is revisited directly in the final Best Practices lesson.

Up next: Blade Templating Basics covers the templating engine behind every view() call you've seen so far.