Replacing controllers with middleware

Middleware is now a very popular topic in the PHP community, here are some of my thougts on the subject. First, let’s take a quick look at what middleware is ( if you already know about middleware you can skip this part):

Short intro

The idea behind it is “wrapping” your application logic with additional request processing logic, and then chaining as much of those wrappers as you like. So when your server receives a request, it would be first processed by your middlewares, and then after you generate a response it will also be processed by the same set:

Middleware

Middleware

It may sound complicated, but in fact it’s very simple if you look at some examples of what could be a middleware:

  • Firewall – check if requests are a allowed from a particular IP
  • JSON Formatter – Parse JSON post data into parameters for your controller. Then turn your response into JSON before sending ti back
  • Authentication – Redirect users who are not logged in to a login page

The coolest part of this is chaining. Since middlewares don’t know about each other it’s simple to find the ones you need and chain them together. And the best part is that after we get PSR-7 we can get sets of middleware that are decoupled from the frameworks and easily interpolable.

This is it for the quick intro, now here are my thoughts:

Replacing controllers
In the picture above notice the application kernel in the middle? My initial thought was: why not consider our application as middleware too ?. Indeed, controllers in our frameworks already read requests and return responses, so pretty much they are also middleware, just without the chaining. The other thing that differs controllers from middleware is tight coupling to the framework, apart from that they are the same. And here it dawned on me:

The application kernel in the above chart shouldn’t be our Controller, since when you follow some proper design rules it’s your models that contain application logic, not the controller. Which means next gen frameworks should dump the Controller concept entirely, and split everything to middleware layers

Problems
There are some problems with middleware though, the biggest on coming from framework independence. The amount of things you can do without utilizing the framework is actualy very small. Interpolable middleware would have no way to access your database, templating, etc. The only way to expose those things in an interpolable way would be for middleware to provide you with a set of required interfaces that you would hvae to satisfy. That’s cool, but it might be far too hard for Junior devs, and eventually not catch on.

Were we using middleware all this time?
All frameworks allow you to specify in your controllers some code that would be executed before and after the action execution, likw this:

class Controller
{
    function before()
    {
        //preprocess, check authorization, do redirects
    }

    function actionIndex()
    {
        //actual action
    }

    function after()
    {
        //postprocess, handle formatting, etc
    }
}

Well in that case your before()/after() has always been your middleware code. And if you wrote your controllers following the thin controller, fat model rule your actions are pretty much middlewares too, since all they do is format data receive from your model layer.

Let’s try inverting
Another issue middleware has that old-style controllers don’t is heavy reliance on configuration. There must be some config file present that will tell which middlewares to chain for a particular route. And what I learned over the years that it’s much better to write code instead of config. You can debug code, it’s harder to debug a misconfigured system. So here I thought, that if controllers and middleware are so simple, perhaps it’s posiible to reverse the idea and write controllers in a middleware fashion, consider this:

class Controller
{
    function actionIndex()
    {
        //assume that each middleware modifies
        //the request/response given
        if(!$this->auth->isLoggedIn($this-request));
            return $this->redirect($this->request);

        $this->json->processRequest($this-request);
        $response = /* call model layer and build a response */;
        $this->json->processResponse($this-response);
        return $response;
    }
}

I think the above is more readable, debuggable and understandable then chaining middleware in a configuration file. So maybe we don’t really need middleware, just better controller code? Maybe the whole point of middleware is to prevent programmers writing spaghetti code in their controllers ?

Is a HTTP Request enough?
The PSR-7 has one of it’s goals to enable interpolable middleware, but it bases its standard on an HTTP Request. The question is whether data in such a representation is enough to writ middlewares, what if you want to pass some additional request parameters around? In the JSON encode/decode example I mentioned earlier it doesn’t really sound like a very good idea to create a new request converting JSON data into POST form encoded data for the next middleware. This decoding/encoding part is an overhead, that I wish we could avoid. Wouldn’t it be better if it could just decode data and pass it like that?

What I’m thinking is perhaps a better idea would be to have a Request class that is more like a parameter bag, and has nothing to do with HTTP. This way it could be used for even CLI apps. The problem with it is how would it represent things like URLs and headers? I don’t know, but there must be a way.

3 Comments

  1. Good thoughts on loosing controllers.
    Also, no spellchecker on your computer?

  2. Hello,
    i have been (and still am) following with a lot of interest the psr-7 implementation of request\response…etc…what is now called http-message.
    I see a lot of people putting a lot of effort into this, i see a lot of different opinions, i see people changing their minds and embracing the new…some others holding back tight.

    I also see what i felt when i started using node.js i.e. that php was a little behind and i wanted all the cool features of node into php, and i wanted them “now”.

    Then sometimes I stop and reflect on what’s going on, because the hunger for new features can make us loose sight of our goals.

    We are now back thinking of a web application as just something that parse a request and return a response. Indeed a web application is just that. That is 101!
    But we are starting to forget that we build applications to solve problems. The application problem is not parsing a request and sending a response. The application is more about calculating a result, storing input, in simpler words, development is more about what happens beetween the request and the response.

    We, those who like to build framework and packages, are not being objective because we think the application being all about request/response.
    That would be true when the problem is “building a framework”. Managing request and response should be the framework responsibility.
    Even if i love using middlewares and i find it very elegant (i love connect/express.js), I believe that having all controllers (route handlers) operate as middlewares will distract the developer from the problem it’s trying to solve.

    Thinking evertything as a middleware will force us have all development in terms on $request / $response parameters.
    A couple of pseudo-examples, imagine these controllers as methods inside a traditional mvc controller class

    //1) as middlewares
    listUsers($request, $response)
    updateUser($request, $response)

    do not look very natural to me

    //2) with problem related parameters
    listUsers($offset, $limit, $sort)
    updateUser($id)

    these look more problem-solving-related to me.

    Being able to use request and response object should be an addon feature (sf2 already does that with parameter type hinting) not the only way of using controllers (intended as route handlers)

    An extra issue arises from http-message immutability.

    Don’t get me wrong, I believe immutability to be a valid point and the way to go, but now every change of state of the response object must be communicated back to the framework (see zf2 example on mvc event setResponse()).
    So not only we are left with one controller method signature – someAction($request, $response) – for all the problems we are trying to solve, but we also need to be responsible for maintaning synchronized states of the request and response objects.

    Again, that should be framework developers’ concern and the framework responsibility.

    Application developers should be concerned with real input parameters, not with their containers. We shoud stop forcing every developer to be a framework developer and provide tools to concentrate on the real application problem.

    Another issue:
    the request/response paradigm does not manage console applications.
    Many frameworks (Yii/Yii2 as an example) abstracts application input/output so that it can be applied both to console and web applications. Basing everything on http-messages we will loose that further level of abstraction. psr-7 is all about http.

    So…big news, big enhancements, more interoperability and cool new features ….but also a lot more to think about.

    best regards

  • Dracony

    April 30, 2015 at 10:35 am

    Well the PSR-7 Request/Response classes are more designed for communication between arts of system than internal use. E.g. PHPixie is going to have its own Request/Response which wrap around PSR classes. As for your example with listUsers() to me it seems like listUsers($offset, $limit, $sort) is your domain logic and should g into some separate class totally unrelated to controllers/middlwares etc. Then your ListController calls that method and generates a response.

Leave a Reply

Your email address will not be published. Required fields are marked *

© 2024 Dracony

Theme by Anders NorénUp ↑