Middleware and CakePHP

I started to migrate some of my CakePHP 3.x apps to CakePHP 3.3 middleware and even tested 3.4 already back in 2016.
Until then I didn’t even try the new PSR7 way. But it was about time I caught up here.
I wanted to actually start using some of the Middleware classes available.

I also always wanted to dig deeper into this topic that hasn’t yet too much blog post coverage it seems.
Now, let’s get started here 1.5 years later – but with some very useful gotchas (later in the article more).

Middleware what?

Yeah, this new PSR7 thing – you can also read more about it here.
In fact, it is not that new, but more and more frameworks have been adopting, and so did CakePHP.
When you are upgrading from 3.3 to a newer minor version, you will stumble upon it.

There are also tons of middleware already available out there that – by design – is supposed to work with any PHP framework.
Check out this impressive list.

But now back to CakePHP 3.

First steps

So what to do first to get this new infrastructure working?
The docs explain it quite well:

  • You update your index.php with the new way of dispatching a request (see cakephp/app‘s index.php).
  • You copy and paste the Application.php skeleton into your src/ directory (App namespace). It should be the only class there.

Then you remove your current bootstrap lines around DispatcherFactory::add(...).
You now can add the same functionality as Middleware again in your Application class.

Hands on

Let’s show it with two of my new Middleware classes, "Maintenance" and the improved "ErrorHandler". The first will be an addition, the second one a replacement.

use Setup\Middleware\MaintenanceMiddleware;
use Tools\Error\Middleware\ErrorHandlerMiddleware;
...
class Application extends BaseApplication {

    /**
     * @param \Cake\Http\MiddlewareQueue $middlewareQueue The middleware queue to setup.
     * @return \Cake\Http\MiddlewareQueue The updated middleware.
     */
    public function middleware($middlewareQueue) {
        $middlewareQueue
            // Add Setup plugin Maintenance middleware
            ->add(MaintenanceMiddleware::class)

            // Improved Tools plugin ErrorHandler middleware
            ->add(ErrorHandlerMiddleware::class)

            // Handle plugin/theme assets like CakePHP normally does.
            ->add(AssetMiddleware::class)

            // Finally apply normal routing
            ->add(RoutingMiddleware::class);

        return $middlewareQueue;
    }

}

Instead of the ::class way you could also instantiate the classes: new ...Middleware().
But I prefer to keep them strings only until used, especially since I don’t need to pass any arguments for them.

When to use, and when not to use.

The middleware opens a few new possibilities, but it can also introduce more bad code.
You should decide wisely, what can/should be transformed into a middleware class.

Useful cases are usually:

  • Basic low level caching.
  • Serving assets (e.g. a custom WYSIWYG editor serving).
  • Manipulating the request early on, or the response afterwards.
  • Authentication and header parsing, e.g. Token based ones
  • Needing a very early on decision point on certain dispatching options.

You should be careful about:

  • Templating/Rending involved, here usually it is better to use controller (+component?) and normal view rendering.
  • Needing more customization on a controller level, on what parts of the app invoke this logic and which don’t (usually can stay an easy to hook in component).

Now to the gotchas I promised:

Why the improved ErrorHandler?

The core one treats all errors the same by default. Even user-triggered "out of bounds" or not found records are logged into the "error" log.
I find that totally wrong – and even harmful – as it cloaks the actual "internal errors" of higher severity.
So the improved handler provides a way to whitelist certain exceptions as "404" ones that can be triggered from visitors or bots and do not represent internal application errors. It uses the referer to decide whether this is "404" (externally triggered), or an actual issue for error.log (internal reference).
This 404.log then can be occasionally checked and removed, while the error.log as such has to be more closely monitored and everything coming in should be fixed ASAP.

More about the implementation details in the Setup plugin docs.
It is important to also switch out the low level handler in your bootstrap here as well as setting up a specific 404 listener.

Tip: If you use DatabaseLog plugin, you can easily set up a monitoring cronjob to alert the admin (e.g. via email) within seconds of severe issues/errors. With the above "separation" you don’t have false positives and will get only "real" alerts. The plugin built in alert system also makes sure errors are batch-sent to not send out too much emails. Of course you can also limit the scope of alerts further to e.g. only very severe exceptions using the configuration.

Maintenance, what’s that about?

I use this to put the whole application into maintenance mode if there is a more time intensive deployment coming up to assert the users don’t upload images during that timeframe, or put something into the database while it migrates which then breaks.
Usually it is only a few seconds – but it helps to keep the integraty of the website intact.

See my blog post about it, also check the Setup plugin documentation.

The best use case is adding those two commands (enable, disable) into your deploy script, at the beginning and the end. But you can also invoke it through command line manually, when you plan on doing some larger migration or maintenance work.

Other useful middlewares

(Browser) Language detection

The I18n middleware sounds useful if you need to provide multi-locale output and want to detect the best default.
The old approach would have been a component, the more modern way is a middleware here then, for example.

Throttling

The Throttle middleware can be used for certain (API) requests to stay under a certain limit of requests per timeframe.

Your recommendations?

You can comment with your recommended middleware classes, CakePHP or otherwise.

4.67 avg. rating (91% score) - 3 votes

2 Comments

  1. Hello Mark.

    I have been using your "MaintenanceMiddleware" since cakephp 3.6 and am now using cakephp 4.4 with success.

    I use it to control the IP addresses that have access and those that should not have access.

    My server just switches to a different IP address each time. How can I, for example, specify a whole class or
    a range of "good" IP addresses in your MaintenanceMiddleware plugin?

    for example, 5.146.197.* or 5.146.196.*?

    Many thanks
    Michael

  2. Dont think that exists yet, as this was mainly to do maintenance from a specific IP
    Feel free to add this feature as PR if you need it.

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.