Controllers

Managing business logic and requests in Controllers

graph TD A[Route] --> B[Controller] B --> C[Form Request Validation] C --> D[Service/Model] D --> E[Business Logic] E --> F[Response] F --> G{Type?} G -->|View| H[Blade View] G -->|JSON| I[JSON Response] G -->|Redirect| J[Redirect Response]

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