Md Mominul Islam | Software and Data Enginnering | SQL Server, .NET, Power BI, Azure Blog

while(!(succeed=try()));

LinkedIn Portfolio Banner

Latest

Home Top Ad

Responsive Ads Here

Post Top Ad

Responsive Ads Here

Thursday, August 28, 2025

Laravel Module 6: Mastering Controllers & Middleware - From Beginner to Advanced

 

Table of Contents

  1. Introduction to Controllers

    • What Are Controllers?

    • Why Use Controllers?

    • Types of Controllers

    • Pros and Cons of Controllers

    • Real-Life Scenarios

  2. Creating Controllers

    • Setting Up a Basic Controller

    • Resource Controllers

    • Single-Action Controllers

    • Example: Task Management System

    • Best Practices and Standards

  3. Controller Methods & Dependency Injection

    • Writing Controller Methods

    • Dependency Injection in Controllers

    • Example: E-commerce Product Management

    • Pros, Cons, and Alternatives

  4. Middleware Overview

    • What Is Middleware?

    • How Middleware Works in Laravel

    • Built-in Middleware

    • Real-Life Use Cases

  5. Applying Middleware to Routes & Groups

    • Route-Specific Middleware

    • Group Middleware

    • Example: Role-Based Dashboard Access

    • Best Practices for Middleware Application

  6. Creating Custom Middleware

    • Building Custom Middleware

    • Example: Restricting Access Based on User Subscription

    • Advanced Middleware Scenarios

    • Pros, Cons, and Alternatives

  7. Conclusion

    • Recap of Key Concepts

    • Next Steps in Your Laravel Journey


1. Introduction to Controllers

What Are Controllers?

In Laravel, controllers are PHP classes that handle the logic for processing HTTP requests and returning responses. They act as the intermediary between your application’s routes and the business logic (models, services, etc.). By organizing related request-handling logic into controllers, you keep your codebase clean, modular, and maintainable.

For example, in a blog application, a PostController might handle tasks like displaying posts, creating new posts, or updating existing ones. Instead of cluttering your routes/web.php file with logic, controllers centralize it for better organization.

Why Use Controllers?

  • Separation of Concerns: Controllers separate routing from business logic, adhering to the MVC (Model-View-Controller) pattern.

  • Reusability: Common logic (e.g., fetching posts) can be reused across multiple routes.

  • Scalability: As your application grows, controllers make it easier to manage complex logic.

  • Testability: Controllers can be unit-tested independently, ensuring robust code.

Types of Controllers

  1. Basic Controllers: Handle general request logic for various routes.

  2. Resource Controllers: Automatically provide methods for CRUD (Create, Read, Update, Delete) operations.

  3. Single-Action Controllers: Handle a single action, ideal for simple tasks.

Pros and Cons of Controllers

Pros:

  • Organized and modular code.

  • Easy to test and maintain.

  • Simplifies complex route logic.

  • Supports dependency injection for better flexibility.

Cons:

  • Overuse can lead to bloated controllers (e.g., "God Controllers").

  • Requires discipline to keep logic lean and delegate to services or repositories.

  • Learning curve for beginners unfamiliar with MVC.

Real-Life Scenarios

  • Task Management App: A TaskController could handle creating, updating, and deleting tasks for users.

  • E-commerce Platform: A ProductController might manage product listings, details, and cart interactions.

  • Social Media Dashboard: A DashboardController could display user-specific data based on their role (admin, user, etc.).


2. Creating Controllers

Setting Up a Basic Controller

Let’s start by creating a simple controller for a Task Management System. We’ll use Laravel’s Artisan CLI to generate a controller.

php artisan make:controller TaskController

This command creates a TaskController.php file in the app/Http/Controllers directory:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class TaskController extends Controller
{
    public function index()
    {
        return view('tasks.index');
    }
}

In your routes/web.php, you can link the controller to a route:

use App\Http\Controllers\TaskController;

Route::get('/tasks', [TaskController::class, 'index'])->name('tasks.index');

This sets up a basic route to display a task list. The index method returns a view located at resources/views/tasks/index.blade.php.

Resource Controllers

Resource controllers simplify CRUD operations by providing predefined methods (index, create, store, show, edit, update, destroy). Let’s create a resource controller for tasks:

php artisan make:controller TaskController --resource

This generates a controller with the following methods:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class TaskController extends Controller
{
    public function index() {}
    public function create() {}
    public function store(Request $request) {}
    public function show($id) {}
    public function edit($id) {}
    public function update(Request $request, $id) {}
    public function destroy($id) {}
}

To register a resource controller in routes/web.php:

Route::resource('tasks', TaskController::class);

This automatically maps the controller methods to RESTful routes:

Verb

URI

Action

Route Name

GET

/tasks

index

tasks.index

GET

/tasks/create

create

tasks.create

POST

/tasks

store

tasks.store

GET

/tasks/{task}

show

tasks.show

GET

/tasks/{task}/edit

edit

tasks.edit

PUT/PATCH

/tasks/{task}

update

tasks.update

DELETE

/tasks/{task}

destroy

tasks.destroy

Example: Task Management SystemLet’s implement a resource controller for tasks. Assume we have a Task model and a tasks table with columns id, title, description, and status.

<?php

namespace App\Http\Controllers;

use App\Models\Task;
use Illuminate\Http\Request;

class TaskController extends Controller
{
    public function index()
    {
        $tasks = Task::all();
        return view('tasks.index', compact('tasks'));
    }

    public function create()
    {
        return view('tasks.create');
    }

    public function store(Request $request)
    {
        $validated = $request->validate([
            'title' => 'required|string|max:255',
            'description' => 'required|string',
            'status' => 'required|in:pending,completed',
        ]);

        Task::create($validated);
        return redirect()->route('tasks.index')->with('success', 'Task created successfully!');
    }

    public function show(Task $task)
    {
        return view('tasks.show', compact('task'));
    }

    public function edit(Task $task)
    {
        return view('tasks.edit', compact('task'));
    }

    public function update(Request $request, Task $task)
    {
        $validated = $request->validate([
            'title' => 'required|string|max:255',
            'description' => 'required|string',
            'status' => 'required|in:pending,completed',
        ]);

        $task->update($validated);
        return redirect()->route('tasks.index')->with('success', 'Task updated successfully!');
    }

    public function destroy(Task $task)
    {
        $task->delete();
        return redirect()->route('tasks.index')->with('success', 'Task deleted successfully!');
    }
}

Blade Views

  • resources/views/tasks/index.blade.php:

<!DOCTYPE html>
<html>
<head>
    <title>Task List</title>
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
    <h1>Tasks</h1>
    @if (session('success'))
        <div class="alert alert-success">{{ session('success') }}</div>
    @endif
    <a href="{{ route('tasks.create') }}">Create New Task</a>
    <ul>
        @foreach ($tasks as $task)
            <li>
                {{ $task->title }} ({{ $task->status }})
                <a href="{{ route('tasks.show', $task) }}">View</a>
                <a href="{{ route('tasks.edit', $task) }}">Edit</a>
                <form action="{{ route('tasks.destroy', $task) }}" method="POST" style="display:inline;">
                    @csrf
                    @method('DELETE')
                    <button type="submit">Delete</button>
                </form>
            </li>
        @endforeach
    </ul>
</body>
</html>
  • resources/views/tasks/create.blade.php:

<!DOCTYPE html>
<html>
<head>
    <title>Create Task</title>
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
    <h1>Create Task</h1>
    <form action="{{ route('tasks.store') }}" method="POST">
        @csrf
        <div>
            <label>Title:</label>
            <input type="text" name="title" value="{{ old('title') }}">
            @error('title') <span>{{ $message }}</span> @enderror
        </div>
        <div>
            <label>Description:</label>
            <textarea name="description">{{ old('description') }}</textarea>
            @error('description') <span>{{ $message }}</span> @enderror
        </div>
        <div>
            <label>Status:</label>
            <select name="status">
                <option value="pending">Pending</option>
                <option value="completed">Completed</option>
            </select>
            @error('status') <span>{{ $message }}</span> @enderror
        </div>
        <button type="submit">Create</button>
    </form>
</body>
</html>

This example demonstrates a fully functional task management system with CRUD operations. Users can view, create, edit, and delete tasks via a clean interface.

Single-Action Controllers

Single-action controllers are used for routes that perform a single task. For example, let’s create a controller to handle sending a welcome email to new users.

php artisan make:controller SendWelcomeEmailController --invokable

This creates a controller with a single __invoke method:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use App\Mail\WelcomeEmail;

class SendWelcomeEmailController extends Controller
{
    public function __invoke(Request $request)
    {
        $user = $request->user();
        Mail::to($user->email)->send(new WelcomeEmail($user));
        return redirect()->back()->with('success', 'Welcome email sent!');
    }
}

Register the controller in routes/web.php:

Route::get('/send-welcome-email', SendWelcomeEmailController::class)->name('send.welcome.email');

When to Use Single-Action Controllers:

  • For simple, one-off actions (e.g., sending emails, generating reports).

  • When you don’t need the full CRUD functionality of a resource controller.

Best Practices and Standards

  • Keep Controllers Thin: Move complex logic to services, repositories, or models.

  • Use Route Model Binding: Automatically inject models (e.g., Task $task) instead of manually fetching them.

  • Validate Input: Always validate user input using $request->validate().

  • Follow RESTful Conventions: Use resource controllers for CRUD operations to maintain consistency.

  • Name Controllers Clearly: Use descriptive names like TaskController or SendWelcomeEmailController.


3. Controller Methods & Dependency Injection

Writing Controller Methods

Controller methods handle specific actions for a route. Each method typically returns a response, such as a view, JSON data, or a redirect. Let’s enhance our TaskController with a method to filter tasks by status.

public function filterByStatus(Request $request)
{
    $status = $request->query('status', 'pending');
    $tasks = Task::where('status', $status)->get();
    return view('tasks.index', compact('tasks', 'status'));
}

Update routes/web.php:

Route::get('/tasks/filter', [TaskController::class, 'filterByStatus'])->name('tasks.filter');

In index.blade.php, add a filter dropdown:

<form action="{{ route('tasks.filter') }}" method="GET">
    <select name="status" onchange="this.form.submit()">
        <option value="pending" {{ $status == 'pending' ? 'selected' : '' }}>Pending</option>
        <option value="completed" {{ $status == 'completed' ? 'selected' : '' }}>Completed</option>
    </select>
</form>

This allows users to filter tasks by their status (pending or completed).

Dependency Injection in Controllers

Laravel’s dependency injection allows you to inject dependencies (e.g., services, repositories) into controller methods or constructors. This promotes loose coupling and testability.

Example: E-commerce Product ManagementLet’s create a ProductController that uses a ProductService to handle business logic.

First, create a ProductService:

php artisan make:service ProductService

In app/Services/ProductService.php:

<?php

namespace App\Services;

use App\Models\Product;

class ProductService
{
    public function getAllProducts()
    {
        return Product::all();
    }

    public function createProduct(array $data)
    {
        return Product::create($data);
    }

    public function findProduct($id)
    {
        return Product::findOrFail($id);
    }
}

Now, inject ProductService into ProductController:

<?php

namespace App\Http\Controllers;

use App\Services\ProductService;
use Illuminate\Http\Request;

class ProductController extends Controller
{
    protected $productService;

    public function __construct(ProductService $productService)
    {
        $this->productService = $productService;
    }

    public function index()
    {
        $products = $this->productService->getAllProducts();
        return view('products.index', compact('products'));
    }

    public function store(Request $request)
    {
        $validated = $request->validate([
            'name' => 'required|string|max:255',
            'price' => 'required|numeric|min:0',
            'stock' => 'required|integer|min:0',
        ]);

        $this->productService->createProduct($validated);
        return redirect()->route('products.index')->with('success', 'Product created!');
    }

    public function show($id)
    {
        $product = $this->productService->findProduct($id);
        return view('products.show', compact('product'));
    }
}

Register the resource controller:

Route::resource('products', ProductController::class);

Why Use Dependency Injection?

  • Modularity: You can swap out the ProductService implementation without changing the controller.

  • Testability: Mock the ProductService in unit tests to isolate controller logic.

  • Reusability: The same service can be used in multiple controllers or commands.

Pros, Cons, and Alternatives

Pros:

  • Cleaner controllers by delegating logic to services.

  • Easier to test and maintain.

  • Promotes single responsibility principle.

Cons:

  • Adds complexity for small applications.

  • Requires understanding of dependency injection concepts.

Alternatives:

  • Repositories: Use repositories for database interactions instead of services.

  • Actions: Use single-action classes (e.g., CreateProductAction) for specific tasks.

  • Model Methods: For simple apps, place logic directly in model methods (not recommended for large apps).


4. Middleware Overview

What Is Middleware?

Middleware in Laravel acts as a filter between HTTP requests and your application’s logic. It can perform tasks like authentication, logging, or modifying requests/responses before they reach the controller or after the response is generated.

For example, Laravel’s auth middleware ensures a user is logged in before accessing a protected route. If not, it redirects them to the login page.

How Middleware Works

Middleware sits between the request and the response:

Request → Middleware → Controller → Middleware → Response

Each middleware can:

  • Modify the request (e.g., add headers).

  • Block the request (e.g., redirect unauthenticated users).

  • Modify the response (e.g., add caching headers).

Built-in Middleware

Laravel provides several built-in middleware, such as:

  • auth: Ensures the user is authenticated.

  • guest: Ensures the user is not authenticated.

  • throttle: Limits the number of requests (e.g., for API rate limiting).

  • can: Authorizes actions based on Laravel’s Gate or Policy.

Real-Life Use Cases

  • Authentication: Restrict access to a dashboard for logged-in users only.

  • Rate Limiting: Prevent abuse in an API by limiting requests per minute.

  • Logging: Log all requests for analytics or debugging.

  • Role-Based Access: Allow only admins to access certain routes.


5. Applying Middleware to Routes & Groups

Route-Specific Middleware

You can apply middleware to individual routes using the middleware method.

Example: Restrict the tasks.index route to authenticated users:

Route::get('/tasks', [TaskController::class, 'index'])->middleware('auth')->name('tasks.index');

If a user is not logged in, they’ll be redirected to the login page.

Group Middleware

For multiple routes, use middleware groups to apply middleware collectively.

Example: Role-Based Dashboard AccessLet’s create a dashboard with role-based access (e.g., admin vs. regular user). Assume we have a users table with a role column (admin or user).

First, define a custom middleware for admin access:

php artisan make:middleware EnsureUserIsAdmin

In app/Http/Middleware/EnsureUserIsAdmin.php:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class EnsureUserIsAdmin
{
    public function handle(Request $request, Closure $next)
    {
        if (Auth::user() && Auth::user()->role === 'admin') {
            return $next($request);
        }

        return redirect('/home')->with('error', 'Unauthorized access.');
    }
}

Register the middleware in app/Http/Kernel.php:

protected $routeMiddleware = [
    // Other middleware...
    'admin' => \App\Http\Middleware\EnsureUserIsAdmin::class,
];

Apply the middleware to a route group in routes/web.php:

Route::middleware(['auth', 'admin'])->group(function () {
    Route::get('/admin/dashboard', [AdminController::class, 'index'])->name('admin.dashboard');
    Route::get('/admin/users', [AdminController::class, 'users'])->name('admin.users');
});

Example: AdminController

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;

class AdminController extends Controller
{
    public function index()
    {
        return view('admin.dashboard');
    }

    public function users()
    {
        $users = User::all();
        return view('admin.users', compact('users'));
    }
}

Blade View (admin/dashboard.blade.php):

<!DOCTYPE html>
<html>
<head>
    <title>Admin Dashboard</title>
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
    <h1>Admin Dashboard</h1>
    <p>Welcome, {{ Auth::user()->name }}!</p>
    <a href="{{ route('admin.users') }}">Manage Users</a>
</body>
</html>

This setup ensures only admin users can access the dashboard and user management pages.

Best Practices for Middleware Application

  • Use Descriptive Names: Name middleware clearly (e.g., EnsureUserIsAdmin).

  • Apply Middleware Sparingly: Avoid overusing middleware on every route to keep performance optimal.

  • Group Related Routes: Use route groups to apply middleware to related routes (e.g., all admin routes).

  • Leverage Laravel’s Built-in Middleware: Use auth, can, or throttle when possible to avoid reinventing the wheel.


6. Creating Custom Middleware

Building Custom Middleware

Let’s create a custom middleware to restrict access based on a user’s subscription status (e.g., for a SaaS application).

php artisan make:middleware RestrictToPremiumUsers

In app/Http/Middleware/RestrictToPremiumUsers.php:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class RestrictToPremiumUsers
{
    public function handle(Request $request, Closure $next)
    {
        if (Auth::user() && Auth::user()->is_premium) {
            return $next($request);
        }

        return redirect('/subscribe')->with('error', 'Please upgrade to a premium plan.');
    }
}

Register the middleware in app/Http/Kernel.php:

protected $routeMiddleware = [
    // Other middleware...
    'premium' => \App\Http\Middleware\RestrictToPremiumUsers::class,
];

Apply it to a route:

Route::get('/premium/feature', [PremiumController::class, 'index'])->middleware(['auth', 'premium'])->name('premium.feature');

Example: Restricting Access Based on Subscription

Assume a SaaS app where premium users can access advanced features (e.g., analytics reports).

PremiumController:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PremiumController extends Controller
{
    public function index()
    {
        return view('premium.index');
    }
}

Blade View (premium/index.blade.php):

<!DOCTYPE html>
<html>
<head>
    <title>Premium Feature</title>
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
    <h1>Premium Analytics</h1>
    <p>Exclusive content for premium users!</p>
</body>
</html>

Advanced Middleware Scenarios

  • Dynamic Middleware Parameters: Pass parameters to middleware for flexibility. For example, restrict access to specific roles:

public function handle(Request $request, Closure $next, $role)
{
    if (Auth::user() && Auth::user()->role === $role) {
        return $next($request);
    }

    return redirect('/home')->with('error', 'Unauthorized access.');
}

Apply it in routes/web.php:

Route::get('/manager/dashboard', [ManagerController::class, 'index'])->middleware('role:manager');
  • Terminable Middleware: Use the terminate method to perform actions after the response is sent (e.g., logging).

public function terminate(Request $request, $response)
{
    \Log::info('Request processed for URL: ' . $request->url());
}

Pros, Cons, and Alternatives

Pros:

  • Reusable logic across routes.

  • Enhances security and access control.

  • Easy to test and maintain.

Cons:

  • Overuse can complicate route definitions.

  • Poorly written middleware can impact performance.

Alternatives:

  • Gates and Policies: Use Laravel’s authorization features for complex permission logic.

  • Route Constraints: Use route constraints (e.g., where) for simple checks.

  • Controller Logic: For one-off checks, handle logic in the controller (not recommended for reusable logic).


7. Conclusion

In this Module 6 of our Laravel Core Complete Course, we’ve explored the power of controllers and middleware in building robust, secure, and scalable web applications. From creating basic and resource controllers to leveraging dependency injection and custom middleware, you now have the tools to structure your application logic effectively and secure your routes.

Key Takeaways

  • Controllers centralize request-handling logic, making your codebase modular and maintainable.

  • Resource Controllers streamline CRUD operations, while Single-Action Controllers are perfect for simple tasks.

  • Dependency Injection promotes loose coupling and testability.

  • Middleware provides a flexible way to filter requests and enforce security.

  • Custom Middleware allows you to implement application-specific logic, such as subscription-based access or role checks.

No comments:

Post a Comment

Thanks for your valuable comment...........
Md. Mominul Islam

Post Bottom Ad

Responsive Ads Here