Intro to Terminable Middleware

Feature image: Intro to Terminable Middleware

This post assumes basic familiarity with Laravel’s request lifecycle and middleware.

Creating and interacting with middleware is a common task for Laravel developers. You’re probably familiar with before and after middleware. Before middleware can be used to authenticate users, set the app language, or limit responses based on the request. After middleware can be used to add cookies or update response headers. In this post, we’ll look at a handy but less utilized type of middleware: Terminable middleware.

Terminable middleware run after your response is sent to the browser, making them uniquely useful for situations where you wish to run some code without blocking the response. For example, you could use terminable middleware for logging information about the request and the response, dispatching emails or notifications, or cleaning up temporary data. For more resource-intensive processes after the request, Laravel queues are a better solution.

Image of the Laravel request lifecycle, described in more detail in the following paragraph.

The illustration above, based on a graphic from Laravel Up and Running, shows where before, after, and terminable middleware are processed during the request lifecycle.

  1. First, the request passes through before middleware before reaching your route or controller.
  2. Then, just before the response is returned to the browser, after middleware is run.
  3. Finally, terminable middleware runs at the end of this request lifecycle.

Definition

Unlike other types of middleware, terminable middleware define a terminate() method rather than handle(). The example below demonstrates this method.

// app/Http/Middleware/MyMiddleware.php
 
class MyMiddleware
{
public function terminate($request, $response): void
{
// run some code
}
}

Apply your middleware globally or on a route/route group basis.

To apply globally, add your middleware class to the $middleware array in the app/Http/Kernel.php class.

// app/Http/Kernel.php
 
protected $middleware = [
// ...
\App\Http\Middleware\MyMiddleware::class,
];

To apply on a route or route group, assign your middleware a key in the $routeMiddleware array in the app/Http/Kernel.php class. Then pass the assigned key you created to the middleware method in your route file.

// app/Http/Kernel.php
 
protected $routeMiddleware = [
// ...
'myMiddleware' => \App\Http\Middleware\MyMiddleware::class,
];
// routes/web.php
 
Route::get('/profile', function () {
//
})->middleware('myMiddleware');

Source Dive

Like other types of middleware, terminable middleware are executed by the HTTP kernel. Let’s take a look at Laravel’s public/index.php file to see when they’re called:

// The `handle()` method, called from the app kernel, calls any
// Before or After middleware defined in middleware classes.
$response = $kernel->handle(
$request = Request::capture()
)->send();
 
// The `terminate()` method calls any Terminable middleware
// defined using a `terminate()` method.
$kernel->terminate($request, $response);

This $kernel->terminate() function later calls the terminateMiddleware() method defined in the Illuminate\Foundation\Http\Kernel class.

/**
* Call the terminate method on any terminable middleware.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Response $response
* @return void
*/
protected function terminateMiddleware($request, $response)
{
$middlewares = $this->app->shouldSkipMiddleware() ? [] : array_merge(
$this->gatherRouteMiddleware($request),
$this->middleware
);
 
foreach ($middlewares as $middleware) {
if (! is_string($middleware)) {
continue;
}
 
[$name] = $this->parseMiddleware($middleware);
 
// creates a fresh instance of your middleware
$instance = $this->app->make($name);
 
if (method_exists($instance, 'terminate')) {
$instance->terminate($request, $response);
}
}
}

This method creates a fresh instance of your middleware. If you have a before or after middleware that is also terminable, and you need the same instance of the middleware class to be re-used before/after the request and for termination, you can bind your middleware to the container as a singleton. Check out the documentation for an example.

Difference between After and Terminable Middleware

You may be wondering how terminable middleware differ from after middleware. After middleware execute before the response is returned to the browser. On the other hand, terminable middleware execute after the response is returned. When deciding which is most appropriate for your application, consider the following:

  • If you need to manipulate the response in any way: Go with after.
  • If you want to do some processing after the response is returned: Try terminable!

Requirements and Limitations

Your server needs to use FastCGI for your middleware’s terminate process to run automatically.

Identifying if FastCGI is available on your server is beyond the scope of this post, but most servers use it. If you try it and it doesn’t work, check with your server administrator.

Running terminable middleware requires PHP’s fastcgi_finish_request() function, which closes the connection to the client while keeping the PHP worker available to finish the request (i.e., execute the code in your terminate() method).

Because of this, expensive processes like complex database queries or external requests can block workers from performing other required tasks and slow your app’s response time, or even cause gateway errors. Queue workers are better suited for these sorts of tasks.

Testing

To test terminable or any other middleware, ensure your middleware run during your test request. Make sure that your test class does not use the WithoutMiddleware trait. Or, you can include middleware for an individual test by calling $this->withMiddleware(); at the top of your test method.

Next Steps

Now that we’ve covered the basics, you can play around with creating your own terminable middleware. Try logging a message or sending an email. If you’ve used terminable middleware before, we’d love to hear how you’ve implemented it in your apps. Send us a tweet @TightenCo!

Get our latest insights in your inbox:

By submitting this form, you acknowledge our Privacy Notice.

Hey, let’s talk.

By submitting this form, you acknowledge our Privacy Notice.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Thank you!

We appreciate your interest. We will get right back to you.