How to Terminate Middleware in Laravel

Updated: January 15, 2024 By: Guest Contributor Post a comment

Introduction

Laravel, a robust PHP framework, comes with many out-of-the-box features that make web application development a breeze. One of such features is Middleware — a type of filtering mechanism that handles HTTP requests entering your application. In Laravel, not only can you perform actions before a request is executed, but also, you can take actions after the response is sent to the browser, this is what we call terminable middleware.

This tutorial will guide you through the process of creating and utilizing terminable middleware in your Laravel applications. We’ll start with the basics and gradually move on to more advanced implementations, with practical examples and expected outputs where applicable.

Understanding Middleware Termination

Middleware termination allows you to perform tasks after a response has been sent to the client. Why might this be useful? It allows you to delay expensive tasks until after the user has received their response, thus improving the perceived performance of your application. Examples of such tasks include logging, sending emails, or performing various clean-up operations.

Creating Basic Terminable Middleware

<?php

namespace App\Http\Middleware;

use Closure;

class TaskTerminator
{
    public function handle($request, Closure $next)
    {
        // Perform action before the request is handled by the application
        return $next($request);
    }

    public function terminate($request, $response)
    {
        // Perform action after the response is sent to the browser
    }
}

In the above example, we have created a simple terminable middleware class named TaskTerminator. The terminate method houses code executed after the response is sent to the client.

Registering Terminable Middleware

To make Laravel aware of the terminable middleware, you need to register it within your application’s global or route middleware stack.

Global Middleware:

protected $middleware = [
    // ...

    \App\Http\Middleware\TaskTerminator::class,

    // other middleware can be listed here
];

Route Middleware:

protected $middleware = [
    // ...

    \App\Http\Middleware\TaskTerminator::class,

    // other middleware can be listed here
];

Implementing an Example

Imagine you want to log the duration of requests. The terminate method could be used to calculate the time from the moment the request is received until after the response is sent back to the client.

<?php

// ...

class TaskTerminator
{
    // ...

    protected $startTime;

    public function __construct()
    {
        $this->startTime = microtime(true);
    }

    public function handle($request, Closure $next)
    {
        // Handling the request
        return $next($request);
    }

    public function terminate($request, $response)
    {
        $endTime = microtime(true);
        Log::info('Request duration: ' . ($endTime - $this->startTime));
    }

    // ...
}

In the above snippet, we store the start time upon constructing the middleware. Once the request flows through the application and the response is sent, the terminate method logs the duration.

Using Terminable Middleware with Queue Jobs

Terminable middleware can also be very effective when combined with queued jobs. You can initiate a queued job in the terminate method, which will be picked up by a worker after the response is served.

<?php

// ...

class TaskTerminator
{
    // ...

    public function terminate($request, $response)
    {
        SomeJob::dispatch();
    }

    // ...
}

The SomeJob::dispatch() method shown above demonstrates how to dispatch a job from within the terminate method. This job will then be processed in the background.

Testing Terminable Middleware

Testing middleware is vital to ensure it behaves as expected. To test terminable middleware, you can simulate a full HTTP request to your application and then assert that the terminable tasks have taken place.

// Example of a test
public function test_request_duration_is_logged()
{
    // Simulate a request
    $response = $this->get('/some-route');

    // Assertions
    Log::shouldReceive('info')
        ->with(Mockery::on(function ($log) {
            return Str::startsWith($log, 'Request duration: ');
        }))
        ->once();

    $response->assertStatus(200);
}

This sample test ensures that a log entry starting with ‘Request duration: ‘ has been made exactly once.

Advanced Implementation

In more advanced scenarios, you might want to handle different types of responses differently within your terminate method. For instance, you can inspect the HTTP status code or even the content of the response:

<?php

// ...

class TaskTerminator
{
    // ...

    public function terminate($request, $response)
    {
        if ($response instanceof JsonResponse && $response->status() == 200) {
            // Handle JSON responses with a 200 OK status.
        }
    }

    // ...
}

In the above code, we’re using an instance check to determine if the response is a JsonResponse and if it has a status code of 200, offering us the chance to execute relevant post-response logic.

Conclusion

In this tutorial, we’ve explored Laravel’s terminable middleware feature, from basic to more sophisticated implementations. Remember, while terminable middleware offers a great way to delegate the heavier tasks until after your users get a quick response, proper care must be taken to ensure these background actions do not significantly affect server performance or stability.