Working with Slim Middleware

Share this article

Slim is a microframework
that offers routing capabilities for easily creating small PHP applications. But an interesting, and powerful, feature is its concept of Middleware. Slim implements the Rack protocol, a pipelining architecture common to many Ruby frameworks. Thus, middleware can be written that wraps the application, and has access to and can affect the app’s environment and request and response objects in a chained manner. I’ve found middleware to be an eloquent solution for implementing various filter-like services in a Slim app, such as authentication and caching. In this article I’ll explain how middleware works and share with you a simple cache example that highlights how you can implement your own custom middleware.

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:

Slim Middleware

The code that is responsible for generating the page’s content is wrapped in several layers of middleware, most importantly the authentication logic and caching logic. The flow of execution passes through each layer and is either allowed to flow through to the next or is diverted. First a check is made to ensure the user is authenticated. If not, flow is interrupted and an HTTP 401 status is returned. Then a check is made to see if a cached copy of the content is available. If it is, flow is interrupted with a cached copy of the page being returned. Other layers of middleware might exist until the flow finally reaches the logic responsible for generating the page. As our middleware methods return, the execution flow bubbles back up through them. The remaining logic of the caching middleware, for instance, would cache the page’s content for later look up.

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 the call() 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’s add() 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 Fotolia

Frequently 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 BoronczykTimothy Boronczyk
View Author

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.

Advanced
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week