Controllers
Managing business logic and requests in Controllers
Controllers are classes that manage business logic and process requests. They are the bridge between routes and models and are responsible for converting HTTP requests into appropriate actions.
Controller Responsibilities:
- Receiving and processing HTTP requests
- Validating data using Form Requests
- Calling Service classes and Model methods
- Managing session and flash messages
- Returning Response (View, JSON, Redirect)
- Managing errors and exceptions
Controller Design Principles:
- <strong>Thin Controllers, Fat Models</strong>: Business logic should be in Service classes or Models, not in Controller
- <strong>Single Responsibility</strong>: Each Controller should manage one resource
- <strong>Dependency Injection</strong>: Using DI for dependencies
- <strong>Form Request Validation</strong>: Using Form Requests for validation
- <strong>Resource Controllers</strong>: Using Resource Controllers for RESTful operations
Types of Controllers:
- <strong>Basic Controllers</strong>: Simple controllers with custom methods
- <strong>Resource Controllers</strong>: RESTful controllers with standard methods
- <strong>API Controllers</strong>: API-specific controllers with JSON responses
- <strong>Single Action Controllers</strong>: Single action controllers with __invoke method
Examples
Simple Controller
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function index()
{
$users = User::all();
return view('users.index', compact('users'));
}
public function show(User $user)
{
return view('users.show', compact('user'));
}
}
A simple Controller that displays users.
Controller with Dependency Injection
<?php
namespace App\Http\Controllers;
use App\Services\UserService;
use App\Http\Requests\StoreUserRequest;
class UserController extends Controller
{
public function __construct(
private UserService $userService
) {}
public function store(StoreUserRequest $request)
{
$user = $this->userService->createUser($request->validated());
return redirect()->route('users.show', $user)
->with('success', 'User created successfully');
}
}
Controller with dependency injection and Form Request validation.
API Controller
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\UserService;
use Illuminate\Http\JsonResponse;
class UserController extends Controller
{
public function __construct(
private UserService $userService
) {}
public function index(): JsonResponse
{
$users = $this->userService->getAllUsers();
return response()->json([
'data' => $users,
'message' => 'Users retrieved successfully'
]);
}
public function show(int $id): JsonResponse
{
$user = $this->userService->getUserById($id);
return response()->json([
'data' => $user,
'message' => 'User retrieved successfully'
]);
}
}
API Controller that returns JSON response.
Single Action Controller
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ShowProfileController extends Controller
{
public function __invoke(Request $request)
{
return view('profile', [
'user' => $request->user()
]);
}
}
// Route:
// Route::get('/profile', ShowProfileController::class);
Single Action Controller with __invoke method.
Controller with Middleware
<?php
namespace App\Http\Controllers;
class UserController extends Controller
{
public function __construct()
{
$this->middleware('auth')->except(['index', 'show']);
$this->middleware('admin')->only(['destroy']);
$this->middleware('throttle:60,1')->only(['store']);
}
public function index()
{
// Public access
}
public function store()
{
// Requires auth, rate limited
}
public function destroy()
{
// Requires auth and admin role
}
}
Applying middleware in constructor.
Controller with Response Helpers
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function index()
{
return view('users.index', ['users' => User::all()]);
}
public function store(Request $request)
{
// Redirect with message
return redirect()->route('users.index')
->with('success', 'User created');
}
public function update(Request $request, User $user)
{
// JSON response
return response()->json([
'message' => 'User updated',
'user' => $user
]);
}
public function destroy(User $user)
{
// Redirect back
return back()->with('success', 'User deleted');
}
}
Using response helpers for different response types.
Controller with Service Layer
<?php
namespace App\Http\Controllers;
use App\Services\OrderService;
use App\Http\Requests\StoreOrderRequest;
class OrderController extends Controller
{
public function __construct(
private OrderService $orderService
) {}
public function store(StoreOrderRequest $request)
{
try {
$order = $this->orderService->createOrder(
$request->validated(),
$request->user()
);
return redirect()->route('orders.show', $order)
->with('success', 'Order created successfully');
} catch (\Exception $e) {
return back()->withErrors(['error' => $e->getMessage()]);
}
}
}
Controller with service layer that handles business logic.
Controller Testing
<?php
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\User;
class UserControllerTest extends TestCase
{
public function test_index_returns_users()
{
User::factory()->count(3)->create();
$response = $this->get('/users');
$response->assertStatus(200);
$response->assertViewIs('users.index');
$response->assertViewHas('users');
}
public function test_store_creates_user()
{
$userData = [
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => 'password123'
];
$response = $this->post('/users', $userData);
$response->assertRedirect();
$this->assertDatabaseHas('users', ['email' => 'john@example.com']);
}
}
Testing Controller with feature tests.
Use Cases
- Managing HTTP requests and responses
- Validating data with Form Requests
- Calling service classes for business logic
- Managing session and flash messages
- Returning views, JSON or redirects
- Managing errors and exceptions
Common Mistakes
- Putting business logic in Controller that should be in Service
- Not using Form Requests for validation
- Fat Controllers violating Single Responsibility
- Not using Dependency Injection
- Hard-coding logic that should be configurable
- Not handling errors and exceptions
Best Practices
- Use Thin Controllers - put logic in Services
- Use Form Requests for validation
- Use Dependency Injection for dependencies
- Use Resource Controllers for RESTful operations
- Use response helpers for consistency
- Handle errors and exceptions properly
- Test controllers
Edge Cases
- Controller with complex business logic
- Controller with multiple dependencies
- Controller with conditional responses
- Controller in API requiring JSON response
- Controller with file uploads
- Controller with long-running operations
Performance Notes
- Thin Controllers are faster than Fat Controllers
- Use eager loading for relationships
- Use caching for expensive operations
- Response time can be improved with service layer optimization
Security Notes
- Always use Form Requests for validation
- Do authorization checks in Form Request or Policy
- Ensure sensitive data is properly protected
- Use middleware for authentication and authorization
- Do input sanitization in validation
Interview Points
- What is the difference between Thin and Fat Controller?
- Why should you use Form Requests?
- What is Single Action Controller and when is it used?
- How can you test Controller?
- What is the difference between Web and API Controller?
- Why shouldn't you put business logic in Controller?
Version Notes
- Laravel 11.x: Improved performance in Controller execution
- Laravel 11.x: Better support for type-hinted responses
- Laravel 10.x: Improved resource controllers
- Laravel 9.x: Improved single action controllers