Understanding Slim Middleware
The Slim documentation compares a Slim application to an onion, where each layer of the onion is middleware. This is an appropriate metaphor. To better understand it though, let’s assume we’re writing an application which makes use of authentication and caching. Our architecture might look like the following illustration:Implementing Middleware
To see how one can goes about implementing custom middleware, let’s look at code that could serve as the caching middleware referenced above. The requirements for implementing any basic Slim middleware component is actually quite minimal. We only need to write a class that extends SlimMiddleware and override thecall()
method. The middleware’s entry point is this call()
method, which we can either return from (thus interrupting the flow of execution) or call the next layer.
<?php
namespace MyMiddleware;
class Cache extends SlimMiddleware
{
protected $db;
public function __construct(PDO $db)
{
$this->db = $db;
}
public function call()
{
$key = $this->app->request()->getResourceUri();
$rsp = $this->app->response();
$data = $this->fetch($key);
if ($data) {
// cache hit... return the cached content
$rsp["Content-Type"] = $data["content_type"];
$rsp->body($data["body"]);
return;
}
// cache miss... continue on to generate the page
$this->next->call();
if ($rsp->status() == 200) {
// cache result for future look up
$this->save($key, $rsp["Content-Type"], $rsp->body());
}
}
protected function fetch($key)
{
$query = "SELECT content_type, body FROM cache
WHERE key = " . $this->db->quote($key);
$result = $this->db->query($query);
$row = $result->fetch(PDO::FETCH_ASSOC);
$result->closeCursor();
return $row;
}
protected function save($key, $contentType, $body)
{
$query = sprintf("INSERT INTO cache (key, content_type, body)
VALUES (%s, %s, %s)",
$this->db->quote($key),
$this->db->quote($contentType),
$this->db->quote($body)
);
$this->db->query($query);
}
}
The call()
method first checks whether the content is available in the cache. If it is, it sets the response’s Content-Type header and body and then returns, short-circuiting the pipeline. If there’s a cache miss, then $this->next->call()
is called to invoke the next middleware layer. When flow returns back to this point from the other middleware calls, a quick check is made on the request status and the relevant data is cached for future look ups.
Because the class extends Slim’s Middleware
class, it has access to the Slim application’s instance through $this->app
, and thus indirectly access to the response and request objects. We can affect the response’s headers by treating it as an array, and the response’s body through it’s body()
method.
The fetch()
and save()
methods are protected helpers which simply wrap the database queries to look up and persist the content. I’ve included them here just to complete the example. It assumes a table cache
exists with the columns key
, content_type
, and body
. Your persistence mechanism may be different depending on your needs. Also, not shown here (for simplicity’s sake) is cache expiration, though it’s trivial to incorporate on your own.
Registering and Configuring Middleware
Registering the middleware is done with Slim’sadd()
method.
<?php
require_once "../vendor/autoload.php";
$app = new SlimSlim();
$app->add(new MyMiddlewareCache($db));
Of course, more than one middleware can be registered by subsequent calls to add()
. Because new middleware surrounds any previously added middleware, this means they must be added in the reverse order that they’ll be called.
<?php
$app = new SlimSlim();
$app->add(new MyMiddlewareCache($db));
$app->add(new MyMiddlewareAuth($db));
// ...
In the example above, the Cache middleware wraps the Slim app, and then Auth middleware wraps Cache. When $app->run()
is called, the flow of execution will resemble that in the illustration above, first entering the authentication middleware and working its way down to the route.
Configuring middleware is generally done through the service’s constructor. In our example I’ve simply passed an active database connection so it can access the cache table, but you can write your class to accept whatever information you may need to customize its behavior. For example, the component could be re-written to accept a handler object that exposes a fetch()
and save()
method; this would allow us to remove the example methods (or use them as a default fallback), and the end-user developer would provide the functionality per their requirements as part of the component’s configuration.
Conclusion
I’ve found middleware to be an eloquent solution for implementing various aspects of a Slim application. In this article I explained how the middleware architecture works and what’s necessary to implement your own middleware. There’s a small repository of extras with some basic middleware examples, such as CSRF protection and HTTP Authentication. I’ve refactored the example here and submitted a pull request, so if you write a useful middleware service, why not consider submitting it to the project so others can benefit as well? Image via FotoliaFrequently Asked Questions about Working with Slim Middleware
What is Slim Middleware and why is it important?
Slim Middleware is a powerful tool in the Slim Framework that allows you to manipulate HTTP requests and responses. It’s important because it provides a way to execute code before and after your Slim application to modify the incoming request or outgoing response. This can be used for a variety of purposes, such as authentication, caching, or logging.
How do I create a Middleware in Slim?
Creating a Middleware in Slim involves defining a class that implements the MiddlewareInterface. This class should have a method called process()
that takes in a ServerRequestInterface and a RequestHandlerInterface. The process()
method is where you can manipulate the request and response.
How do I add Middleware to my Slim application?
Middleware can be added to your Slim application using the add()
method. This method takes in an instance of your Middleware class. Middleware is executed in the order it is added, so the last Middleware added will be the first one executed.
Can I use Middleware for specific routes in Slim?
Yes, Middleware can be applied to specific routes in Slim. This is done by calling the add()
method on the Route object instead of the App object. This allows you to have Middleware that only affects certain routes.
What is the difference between global and route Middleware?
Global Middleware is applied to every request handled by your Slim application, while route Middleware is only applied to specific routes. This allows you to have different Middleware for different parts of your application.
How can I use Middleware for error handling in Slim?
Middleware can be used for error handling in Slim by catching exceptions in your Middleware class. You can then modify the response to include error information, or redirect the user to an error page.
Can I use Middleware to authenticate users in Slim?
Yes, Middleware is often used for authentication in Slim. This can be done by checking for a valid session or token in the Middleware, and returning an error response if the user is not authenticated.
How can I use Middleware for logging in Slim?
Middleware can be used for logging by writing information about the request and response to a log file. This can be useful for debugging or monitoring your application.
Can I use third-party Middleware with Slim?
Yes, Slim supports third-party Middleware. This can be added to your application in the same way as your own Middleware. This allows you to take advantage of existing Middleware for common tasks.
How can I test my Slim Middleware?
Testing Slim Middleware involves creating a mock request and response, and passing them to your Middleware. You can then assert that the Middleware behaves as expected, such as modifying the request or response, or throwing an exception.
Timothy Boronczyk is a native of Syracuse, New York, where he lives with no wife and no cats. He has a degree in Software Application Programming, is a Zend Certified Engineer, and a Certified Scrum Master. By day, Timothy works as a developer at ShoreGroup, Inc. By night, he freelances as a writer and editor. Timothy enjoys spending what little spare time he has left visiting friends, dabbling with Esperanto, and sleeping with his feet off the end of his bed.