Skip to content

Laravel - Controllers

Controllers are a fundamental part of the Model-View-Controller (MVC) architectural pattern. In Laravel, controllers are responsible for handling incoming HTTP requests, retrieving or manipulating data (often via Models), and then loading an appropriate View or returning a JSON response.

Instead of defining all your request handling logic as Closures in route files, you may wish to organize this behavior using Controller classes. Controllers can group related HTTP request handling logic into a single class.

You can generate a new controller using the Artisan CLI command:

php artisan make:controller <ControllerName>

Replace <ControllerName> with the name of your controller (e.g., UserController, PhotoController). By convention, controller names are singular and end with Controller.

For example, to create a ProductController:

php artisan make:controller ProductController

This command will create a new controller file at app/Http/Controllers/ProductController.php. A basic controller looks like this:

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request; // Automatically included
class ProductController extends Controller
{
// Methods to handle requests will go here
public function index()
{
// Logic to display a list of products
return view('products.index'); // Example: returns a Blade view
}
public function show(string $id)
{
// Logic to display a specific product
// Example: Product::findOrFail($id);
return view('products.show', ['productId' => $id]);
}
}

Once you have a controller method, you can route to it from your routes/web.php or routes/api.php file:

use App\Http\Controllers\ProductController;
// Routes to the 'index' method in ProductController
Route::get('/products', [ProductController::class, 'index']);
// Routes to the 'show' method, passing an 'id' parameter
Route::get('/products/{id}', [ProductController::class, 'show']);

This syntax [ControllerName::class, 'methodName'] is the recommended way as it provides better IDE support and refactorability.

Middleware can be assigned to controller routes in your route files, or you can specify middleware within your controller’s constructor or methods. This allows you to apply filters (like authentication or authorization) to specific controller actions.

Assigning Middleware in the Controller’s Constructor:

Section titled “Assigning Middleware in the Controller’s Constructor:”

You can use the middleware method within your controller’s constructor to assign middleware to actions within the controller. You can even constrain the middleware to only run for certain methods.

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UserProfileController extends Controller
{
public function __construct()
{
// Apply 'auth' middleware to all methods in this controller
$this->middleware('auth');
// Apply 'subscribed' middleware only to the 'settings' method
$this->middleware('subscribed')->only('settings');
// Apply 'admin' middleware to all methods except 'show'
$this->middleware('admin')->except('show');
}
public function show(string $id)
{
return view('profile.show', ['userId' => $id]);
}
public function edit(string $id)
{
// Only authenticated users can reach this
return view('profile.edit', ['userId' => $id]);
}
public function settings()
{
// Only authenticated and subscribed users can reach this
return view('profile.settings');
}
}

Alternatively, and often preferred for clarity, assign middleware directly in your route definitions:

use App\Http\Controllers\UserProfileController;
Route::get('/profile/{id}', [UserProfileController::class, 'show']);
Route::get('/profile/{id}/edit', [UserProfileController::class, 'edit'])
->middleware('auth');
Route::get('/profile/settings', [UserProfileController::class, 'settings'])
->middleware(['auth', 'subscribed']);

For applications that manage resources (like photos, posts, products), Laravel’s resource controllers make it easy to build RESTful controllers around these resources. When you create a resource controller, Laravel pre-fills it with methods for common CRUD (Create, Read, Update, Delete) operations.

Use the --resource or -r flag with the make:controller command:

php artisan make:controller PhotoController --resource

This generates app/Http/Controllers/PhotoController.php with methods like index, create, store, show, edit, update, and destroy.

Example PhotoController (simplified stubs):

Section titled “Example PhotoController (simplified stubs):”
<?php
namespace App\Http\Controllers;
use App\Models\Photo; // Assuming you have a Photo model
use Illuminate\Http\Request;
use Illuminate\View\View;
use Illuminate\Http\RedirectResponse;
class PhotoController extends Controller
{
public function index(): View
{
// $photos = Photo::all();
// return view('photos.index', ['photos' => $photos]);
return view('photos.index');
}
public function create(): View
{
return view('photos.create');
}
public function store(Request $request): RedirectResponse
{
// $validated = $request->validate(['title' => 'required|string|max:255']);
// Photo::create($validated);
// return redirect()->route('photos.index')->with('success', 'Photo created!');
return redirect()->route('photos.index');
}
public function show(string $id): View // Or Photo $photo for route model binding
{
// $photo = Photo::findOrFail($id);
// return view('photos.show', ['photo' => $photo]);
return view('photos.show', ['photoId' => $id]);
}
public function edit(string $id): View // Or Photo $photo
{
// $photo = Photo::findOrFail($id);
// return view('photos.edit', ['photo' => $photo]);
return view('photos.edit', ['photoId' => $id]);
}
public function update(Request $request, string $id): RedirectResponse // Or Photo $photo
{
// $photo = Photo::findOrFail($id);
// $validated = $request->validate(['title' => 'required|string|max:255']);
// $photo->update($validated);
// return redirect()->route('photos.show', $photo)->with('success', 'Photo updated!');
return redirect()->route('photos.index');
}
public function destroy(string $id): RedirectResponse // Or Photo $photo
{
// $photo = Photo::findOrFail($id);
// $photo->delete();
// return redirect()->route('photos.index')->with('success', 'Photo deleted!');
return redirect()->route('photos.index');
}
}

To register all the necessary routes for a resource controller, use Route::resource:

// In routes/web.php
use App\Http\Controllers\PhotoController;
Route::resource('photos', PhotoController::class);

This single line creates multiple routes to handle a variety of actions on the resource. Here’s what it typically generates:

VerbURIActionRoute Name
GET/photosindexphotos.index
GET/photos/createcreatephotos.create
POST/photosstorephotos.store
GET/photos/{photo}showphotos.show
GET/photos/{photo}/editeditphotos.edit
PUT/PATCH/photos/{photo}updatephotos.update
DELETE/photos/{photo}destroyphotos.destroy

Tip: If you only need a subset of resource routes, use only or except methods: Route::resource('photos', PhotoController::class)->only(['index', 'show']);

If a controller handles only a single action, you can create a single action controller by placing a single __invoke method within the controller. This can simplify controllers that have a very specific, singular purpose.

Use the --invokable or -i flag with make:controller:

php artisan make:controller ProvisionServer --invokable

This generates app/Http/Controllers/ProvisionServer.php with an __invoke method:

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\Response; // Or other specific response types
class ProvisionServer extends Controller
{
/**
* Handle the incoming request.
*/
public function __invoke(Request $request): Response
{
// Logic to provision a server...
return response('Server provisioning started.');
}
}

When registering routes for single action controllers, you do not need to specify a method:

// In routes/web.php
use App\Http\Controllers\ProvisionServer;
Route::post('/server/provision', ProvisionServer::class);

Laravel’s service container is used to resolve all Laravel controllers. As a result, you can type-hint any dependencies your controller may need in its constructor or action methods. The dependencies will automatically be resolved and injected.

You can type-hint dependencies in the controller’s constructor. Laravel will automatically inject instances of these services.

Example: Injecting a custom service UserService.

// app/Services/UserService.php (Example Service)
namespace App\Services;
class UserService
{
public function getActiveUsersCount(): int
{
// Imagine logic to count active users
return 150;
}
}
// app/Http/Controllers/DashboardController.php
namespace App\Http\Controllers;
use App\Services\UserService;
use Illuminate\View\View;
class DashboardController extends Controller
{
private UserService $userService;
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
public function index(): View
{
$activeUsers = $this->userService->getActiveUsersCount();
return view('dashboard.index', ['activeUsers' => $activeUsers]);
}
}

In addition to constructor injection, you can also type-hint dependencies on your controller’s action methods. This is useful for dependencies that are only needed by a specific action.

Laravel’s Illuminate\Http\Request object is a common example of method injection.

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Services\OrderProcessorService; // Another example service
use Illuminate\Http\JsonResponse;
class OrderController extends Controller
{
// Example of method injection for Request and a custom service
public function store(Request $request, OrderProcessorService $processor): JsonResponse
{
$validatedData = $request->validate([
'product_id' => 'required|exists:products,id',
'quantity' => 'required|integer|min:1',
]);
$order = $processor->createNewOrder($validatedData);
return response()->json(['message' => 'Order created!', 'order_id' => $order->id], 201);
}
}

Practical Application: Building a User Management Module

Imagine you’re building a user management section for an admin panel. You might create a UserManagementController as a resource controller.

  • index() would list all users (with pagination).
  • create() would show a form to add a new user.
  • store() would validate and save the new user.
  • edit() would show a form to edit an existing user.
  • update() would validate and update the user.
  • destroy() would delete a user. You’d apply auth and perhaps an admin role middleware to protect these routes. Dependency injection could be used to inject a UserRepository or UserService for handling data operations.

Common Learning Obstacle: Fat Controllers

A common mistake is putting too much logic directly into controllers (making them “fat”). Try to keep controllers lean by delegating business logic to Service classes, data access to Repositories or Eloquent models, and complex request validation to Form Request classes. Controllers should primarily orchestrate the flow: receive request, call services/models, return response.

For more details, visit the official Laravel documentation on Controllers: https://laravel.com/docs/controllers