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.
view() call you've seen so far.