Middleware

This document assumes you’ve installed hiraeth/bootstrap or a combination of hiraeth/diactoros and hiraeth/middleland. If you haven’t your mileage may vary. See the installation docs for more information.

Hiraeth supports PSR-15 middleware implementing Psr\Http\Server\MiddlewareInterface. In addition to being able to add middleware from third-party sources, a handful of middleware are installed/provided in hiraeth/bootstrap out of the box:

Middleware Description
client-ip Provides accurate client IP as a Request attribute (client-ip)
encrypt-cookies Encrypts cookies for added security
session Activates the user session and handles response cookies
csrf-token Validates csrf::token input field
firewall A simple IP based firewall
templates Renders templates directly (no routing / action) based URL path
routing Resolves and executes routes

Creating a Middleware

Middleware needs to implement the Psr\Http\Server\MiddlewareInterface

namespace Acme\Middleware;

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;

use Psr\Http\Server\MiddlewareInterface as Middleware;
use Psr\Http\Server\RequestHandlerInterface as Handler;

class Foo implements Middleware
{
	public function process(Request $request, Handler $handler): Response
	{
		//
		// Modify request or return response early.
		//

		$response = $handler->handle($request);

		//
		// Modify the response
		//

		return $response;
	}
}

Registering Middleware

Middleware can be registered by adding a new configuration file in config/middleware folder. The only required property is the class name. In this example, we’ll create config/middleware/foo.jin:

[middleware]
	;
	; The middleware class to register
	;

	class = Acme\Middleware\Foo

Priorities

Since middlewares are registered in separate configuration files, they should have an explicit priority set if they need to occur at a specific point in the stack.:

[middleware]
	;
	; Where in the stack should this be run? Lower priority is first in,
	; last out.
	;

	priority = 22

	;
	; The middleware class to register
	;

	class = Acme\Middleware\Foo

The default priority is 50 and the maximum used by official packages is 100 (the router).

If you are releasing a package that integrates with Hiraeth, it is suggested you use the following reference to determine an appropriate priority for your middleware. End-users can use this as a reference to get an idea where they may wish to place their own middleware with respect to others.

Priority Description
0 - 10 Error / exception handling middleware, request normalization, session handling.
11 - 20 Reserved for end-users.
21 - 30 Early returners like redirectors, IP based firewalls, etc.
31 - 40 Reserved for end-users.
41 - 50 Authentication, content security, etc.
51 - 60 Reserved for end-users.
61 - 70 Content modifiers, compression, etc.
71 - 80 Reserved for end-users.
81 - 90 Content response handlers
91 - 99 Reserved for end-users.

Disabling Middleware

If you need to disable a middleware quickly, then the easiest way is simply to add a disabled property to the configuration and set the value to TRUE.

[middleware]

	;
	; Whether or not this middleware is disabled
	;

	disabled = true

	;
	; Where in the stack should this be run? Lower priority is first in,
	; last out.
	;

	priority = 22

	;
	; The middleware class to register
	;

	class = Acme\Middleware\Foo

Dependency Injection

Middleware classes will be constructed via Hiraeth’s dependency injector and will therefore be automatically and recursively injected with simple dependencies and dependencies for which there are already delegates/providers registered.

public function __construct(Acme\Foo\Service $service)
{
	$this->service = $service;
}

If your middleware has complex dependencies or additional configuration options, you may want to write a delegate or provider to handle its construction and/or configuration. While many other registered services use [&.options] key to enable passing options directly to the constructor, many middleware do not support constructor based option setting. For this reason, the delegate is fully responsible for getting and setting all configuration values.

The creation of these delegates is made a bit easier by extending Hiraeth\Middleware\AbstractDelegate:

class MiddlewareDelegate extends Hiraeth\Middleware\AbstractDelegate
{
	protected static $defaultOptions = [
		'constructoption' => 10
		'setteroption'    => 'value'
	];


	public static function getClass(): string
	{
		return My\Middleware::class;
	}


	public function __invoke(Hiraeth\Application $app): object
	{
		$options  = $this->getOptions();
		$instance = new My\Middleware($options['constructoption']);

		$instance->setOption($options['setteroption']);

		return $instance;
	}
}

Now you can add the options to your middleware config:

[middleware]

	...

	[&.options]

		constructoption = 15

		setteropation = 'customvalue'