Laravel Invokable Controllers: A Complete Guide for Modern PHP Development

Asfia AimanAsfia Aiman
5 min read

Laravel Invokable Controllers are a powerful feature that can significantly improve your application's architecture and code organization. In this comprehensive guide, we'll explore what invokable controllers are, why you should use them, and how to implement them effectively in Laravel 12.

What are Laravel Invokable Controllers?

Invokable Controllers in Laravel are single-action controllers that contain only one public method: the __invoke() method. Unlike traditional controllers that may handle multiple actions, invokable controllers follow the Single Responsibility Principle by focusing on one specific task.

When Laravel encounters an invokable controller in your routes, it automatically calls the __invoke() method, making your routing cleaner and more intuitive.

Why Should You Use Invokable Controllers in Laravel?

1. Enhanced Code Organization

Invokable controllers promote better code organization by separating concerns. Each controller handles exactly one action, making your codebase more modular and easier to navigate.

2. Improved Readability and Maintainability

With single-purpose controllers, developers can quickly understand what each controller does without scanning through multiple methods. This improves code readability and reduces maintenance overhead.

3. Better Testing Strategy

Testing becomes more straightforward when each controller has a single responsibility. You can write focused unit tests that cover one specific functionality without worrying about side effects from other methods.

4. Cleaner Route Definitions

Invokable controllers make your route definitions more elegant and self-documenting. Instead of specifying both controller and method, you only need to reference the controller class.

5. Follows SOLID Principles

Invokable controllers naturally adhere to the Single Responsibility Principle, one of the core SOLID principles of object-oriented programming.

When Should You Use Invokable Controllers?

Perfect Use Cases:

1. Single-Purpose Actions

  • User registration

  • Password reset requests

  • File uploads

  • Email sending

  • API endpoints with one specific function

2. Form Handling When you have forms that perform one specific action, invokable controllers are ideal:

// ContactFormController handles only contact form submissions
Route::post('/contact', ContactFormController::class);

3. API Endpoints For RESTful APIs where each endpoint has a single, well-defined purpose:

// GetUserProfileController returns user profile data
Route::get('/api/user/profile', GetUserProfileController::class);

4. Command-like Operations Actions that represent commands in your domain:

  • ProcessPaymentController

  • SendNotificationController

  • GenerateReportController

When NOT to Use Invokable Controllers:

1. CRUD Operations For traditional CRUD operations, resource controllers are more appropriate as they handle multiple related actions (index, show, create, store, edit, update, destroy).

2. Complex Workflows If your controller needs to handle multiple related actions or has complex branching logic, a traditional controller might be more suitable.

How to Create and Use Invokable Controllers in Laravel 12

Creating an Invokable Controller

Use the Artisan command with the --invokable flag:

php artisan make:controller SendNewsletterController --invokable

This generates a controller with the following structure:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class SendNewsletterController extends Controller
{
    /**
     * Handle the incoming request.
     */
    public function __invoke(Request $request)
    {
        // Your controller logic here
    }
}

Practical Example: User Registration Controller

Here's a real-world example of an invokable controller for user registration:

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Http\Requests\RegisterRequest;
use App\Models\User;
use App\Services\UserRegistrationService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;

class RegisterUserController extends Controller
{
    public function __construct(
        private UserRegistrationService $registrationService
    ) {}

    /**
     * Handle user registration request.
     */
    public function __invoke(RegisterRequest $request): JsonResponse|RedirectResponse
    {
        $user = $this->registrationService->register(
            $request->validated()
        );

        if ($request->expectsJson()) {
            return response()->json([
                'message' => 'User registered successfully',
                'user' => $user
            ], 201);
        }

        return redirect()->route('dashboard')
            ->with('success', 'Registration successful!');
    }
}

Route Definition

// routes/web.php
use App\Http\Controllers\Auth\RegisterUserController;

Route::post('/register', RegisterUserController::class)
    ->name('register');

Best Practices for Laravel Invokable Controllers

1. Use Descriptive Naming

Name your controllers based on the action they perform:

  • SendWelcomeEmailController

  • ProcessPaymentController

  • UpdateUserProfileController

2. Leverage Dependency Injection

Use Laravel's service container to inject dependencies:

public function __construct(
    private EmailService $emailService,
    private UserRepository $userRepository
) {}

3. Use Form Request Validation

Always validate input using Form Request classes:

public function __invoke(CreatePostRequest $request)
{
    // Request is automatically validated
    $validatedData = $request->validated();
}

4. Keep Controllers Thin

Move business logic to service classes:

public function __invoke(ProcessOrderRequest $request)
{
    $order = $this->orderService->process($request->validated());

    return response()->json($order);
}

5. Return Appropriate Responses

Handle both web and API responses gracefully:

public function __invoke(Request $request)
{
    $result = $this->service->performAction($request->all());

    if ($request->expectsJson()) {
        return response()->json($result);
    }

    return redirect()->back()->with('success', 'Action completed!');
}

Performance Benefits in Laravel 12

Laravel 12 includes several optimizations that make invokable controllers even more efficient:

1. Improved Route Caching Invokable controllers work seamlessly with Laravel's route caching, resulting in faster route resolution.

2. Better Memory Usage Single-purpose controllers typically have smaller memory footprints compared to large controllers with multiple methods.

3. Enhanced Auto-loading Laravel 12's improved auto-loading mechanisms work more efficiently with focused, single-purpose classes.

Common Patterns and Examples

File Upload Controller

class HandleFileUploadController extends Controller
{
    public function __invoke(FileUploadRequest $request)
    {
        $file = $request->file('upload');
        $path = $file->store('uploads', 'public');

        return response()->json(['path' => $path]);
    }
}

API Resource Controller

class GetUserPostsController extends Controller
{
    public function __invoke(User $user)
    {
        return PostResource::collection(
            $user->posts()->published()->paginate(15)
        );
    }
}

Command Handler Controller

class ProcessRefundController extends Controller
{
    public function __invoke(ProcessRefundRequest $request)
    {
        $refund = $this->refundService->process($request->validated());

        event(new RefundProcessed($refund));

        return redirect()->route('orders.show', $refund->order_id)
            ->with('success', 'Refund processed successfully');
    }
}

Testing Invokable Controllers

Testing invokable controllers is straightforward and focused:

class RegisterUserControllerTest extends TestCase
{
    public function test_user_can_register_successfully()
    {
        $userData = [
            'name' => 'John Doe',
            'email' => 'john@example.com',
            'password' => 'password123',
            'password_confirmation' => 'password123'
        ];

        $response = $this->post('/register', $userData);

        $response->assertRedirect('/dashboard');
        $this->assertDatabaseHas('users', [
            'email' => 'john@example.com'
        ]);
    }
}

Conclusion

Laravel Invokable Controllers are a powerful tool for creating clean, maintainable, and focused code. They work exceptionally well for single-purpose actions and help enforce good architectural practices. In Laravel 12, they're more performant and easier to work with than ever before.

Consider using invokable controllers when you have single-purpose actions, want to improve code organization, or need to create focused, testable units of functionality. They're particularly valuable in modern Laravel applications that emphasize clean architecture and separation of concerns.

By following the best practices outlined in this guide, you'll be able to leverage invokable controllers effectively in your Laravel 12 applications, resulting in more maintainable and professional code.

0
Subscribe to my newsletter

Read articles from Asfia Aiman directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Asfia Aiman
Asfia Aiman

Hey Hashnode community! I'm Asfia Aiman, a seasoned web developer with three years of experience. My expertise includes HTML, CSS, JavaScript, jQuery, AJAX for front-end, PHP, Bootstrap, Laravel for back-end, and MySQL for databases. I prioritize client satisfaction, delivering tailor-made solutions with a focus on quality. Currently expanding my skills with Vue.js. Let's connect and explore how I can bring my passion and experience to your projects! Reach out to discuss collaborations or learn more about my skills. Excited to build something amazing together! If you like my blogs, buy me a coffee here https://www.buymeacoffee.com/asfiaaiman