Middleware

Basics

The middleware library provides developers a way of defining route middleware for their applications. Middleware are simply layers of request processing before and after a controller action is invoked. This is extremely useful for actions like authorization, logging, and request/response decoration.

Middleware have a simple signature:

use Aphiria\Net\Http\{IRequest, IRequestHandler, IResponse};

interface IMiddleware
{
    public function handle(IRequest $request, IRequestHandler $next): IResponse;
}

IMiddleware::handle() takes in the current request and

  1. Optionally manipulates it
  2. Passes the request on to the next request handler in the pipeline
  3. Optionally manipulates the response returned by the next request handler
  4. Returns the response

Manipulating the Request

To manipulate the request before it gets to the controller, make changes to it before calling $next->handle($request):

use Aphiria\Middleware\IMiddleware;
use Aphiria\Net\Http\{IRequest, IRequestHandler, IResponse};

final class RequestManipulator implements IMiddleware
{
    public function handle(IRequest $request, IRequestHandler $next): IResponse
    {
        // Do our work before returning $next->handle($request)
        $request->getProperties()->add('Foo', 'bar');

        return $next->handle($request);
    }
}

Manipulating the Response

To manipulate the response after the controller has done its work, do the following:

use Aphiria\Middleware\IMiddleware;
use Aphiria\Net\Http\{IRequest, IRequestHandler, IResponse};

final class ResponseManipulator implements IMiddleware
{
    public function handle(IRequest $request, IRequestHandler $next): IResponse
    {
        $response = $next->handle($request);

        // Make our changes
        $response->getHeaders()->add('Foo', 'bar');

        return $response;
    }
}

Middleware Parameters

Occasionally, you'll find yourself wanting to pass primitive values to middleware to indicate something such as a required role to execute an action. In these cases, your middleware should extend ParameterizedMiddleware:

use Aphiria\Middleware\ParameterizedMiddleware;
use Aphiria\Net\Http\{IRequest, IRequestHandler, IResponse};

final class RoleMiddleware extends ParameterizedMiddleware
{
    // Inject any dependencies your middleware needs
    public function __construct(private IAuthService $authService) {}

    public function handle(IRequest $request, IRequestHandler $next): IResponse
    {
        $accessToken = null;

        if (
            !$request->getHeaders()->tryGetFirst('Authorization', $accessToken)
            || !$this->authService->accessTokenIsValid($accessToken)
        ) {
            return new Response(401);
        }

        // Parameters are available via $this->getParameter()
        if (!$this->authService->accessTokenHasRole($accessToken, $this->getParameter('role'))) {
            return new Response(403);
        }

        return $next->handle($request);
    }
}

To actually specify role, pass it into your route configuration:

use Aphiria\Routing\Attributes\{Delete, Middleware};

final class UserController extends Controller
{
    #[
        Delete('users/:userId'), 
        Middleware(RoleMiddleware::class, parameters: ['role' => 'admin'])
    ]
    public function deleteUser(int $userId): void
    {
        // ...
    }
}

Executing Middleware

If you're using the skeleton app, middleware will be executed automatically for you. You can define both global middleware and route middleware.

If you're not using the skeleton app, you'll have to set up a pipeline to execute your middleware for you. Typically, middleware are wrapped in request handlers (eg MiddlewareRequestHandler) and executed in a pipeline. You can create this pipeline using MiddlewarePipelineFactory:

use Aphiria\Middleware\MiddlewarePipelineFactory;

// Assume these are defined by your application
$loggingMiddleware = new LoggingMiddleware();
$authMiddleware = new AuthenticationMiddleware();
$controllerHandler = new ControllerRequestHandler();

$pipeline = (new MiddlewarePipelineFactory)->createPipeline(
    [$loggingMiddleware, $authMiddleware],
    $controllerHandler
);

$pipeline will itself be a request handler, which you can then send a request through and receive a response:

$request = (new RequestFactory)->createRequestFromSuperglobals($_SERVER);
$response = $pipeline->handle($request);