Middleware

Create PSR-15 compliant middleware to process requests and responses

What is Middleware?

Middleware provides a convenient mechanism for filtering HTTP requests entering your application. Larafony uses PSR-15 middleware, making it compatible with any PSR-15 compliant middleware.

Creating Middleware

Implement the MiddlewareInterface from PSR-15:

<?php

declare(strict_types=1);

namespace App\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class LogRequestMiddleware implements MiddlewareInterface
{
    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        // Before request
        $start = microtime(true);

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

        // After request
        $duration = microtime(true) - $start;
        error_log("Request to {$request->getUri()} took {$duration}s");

        return $response;
    }
}

Attaching Middleware to Routes

Use the #[Middleware] attribute to attach middleware to specific routes:

<?php

namespace App\Controllers;

use App\Middleware\AuthMiddleware;
use Larafony\Framework\Routing\Advanced\Attributes\{Route, Middleware};
use Larafony\Framework\Web\Controller;
use Psr\Http\Message\ResponseInterface;

class AdminController extends Controller
{
    #[Route('/admin/dashboard', 'GET')]
    #[Middleware(AuthMiddleware::class)]
    public function dashboard(): ResponseInterface
    {
        // Only accessible if AuthMiddleware passes

        return $this->render('admin.dashboard');
    }
}

Authentication Middleware Example

Here's a complete authentication middleware:

<?php

declare(strict_types=1);

namespace App\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Larafony\Framework\Http\Response\RedirectResponse;

class AuthMiddleware implements MiddlewareInterface
{
    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        // Check if user is authenticated
        $session = $request->getAttribute('session');

        if (!$session || !$session->has('user_id')) {
            // Not authenticated - redirect to login
            return new RedirectResponse('/login');
        }

        // Authenticated - continue
        return $handler->handle($request);
    }
}

CORS Middleware Example

Add CORS headers to API responses:

<?php

namespace App\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class CorsMiddleware implements MiddlewareInterface
{
    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        // Handle preflight requests
        if ($request->getMethod() === 'OPTIONS') {
            return new Response(200, [
                'Access-Control-Allow-Origin' => '*',
                'Access-Control-Allow-Methods' => 'GET, POST, PUT, DELETE, OPTIONS',
                'Access-Control-Allow-Headers' => 'Content-Type, Authorization',
            ]);
        }

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

        // Add CORS headers
        return $response
            ->withHeader('Access-Control-Allow-Origin', '*')
            ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
            ->withHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    }
}

Request Transformation

Middleware can modify the request before it reaches the controller:

<?php

namespace App\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class AddUserToRequestMiddleware implements MiddlewareInterface
{
    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        $session = $request->getAttribute('session');

        if ($session && $session->has('user_id')) {
            // Load user from database
            $user = User::query()->find($session->get('user_id'));

            // Add user to request attributes
            $request = $request->withAttribute('user', $user);
        }

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

Access the user in your controller:

#[Route('/profile', 'GET')]
#[Middleware(AddUserToRequestMiddleware::class)]
public function profile(ServerRequestInterface $request): ResponseInterface
{
    $user = $request->getAttribute('user');

    return $this->render('profile', ['user' => $user]);
}

Multiple Middleware

Stack multiple middleware on a single route:

#[Route('/admin/users', 'GET')]
#[Middleware(AuthMiddleware::class)]
#[Middleware(AdminMiddleware::class)]
#[Middleware(LogRequestMiddleware::class)]
public function index(): ResponseInterface
{
    // Protected by three middleware layers

    return $this->render('admin.users');
}
Execution Order: Middleware executes in the order listed. In this example: Auth → Admin → LogRequest → Controller → LogRequest → Admin → Auth

JSON API Middleware

Ensure all responses are JSON:

<?php

namespace App\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class JsonResponseMiddleware implements MiddlewareInterface
{
    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        $response = $handler->handle($request);

        // Ensure Content-Type is application/json
        if (!$response->hasHeader('Content-Type')) {
            $response = $response->withHeader('Content-Type', 'application/json');
        }

        return $response;
    }
}

Global Middleware

Register middleware globally in bootstrap/app.php:

$app->withMiddleware([
    LogRequestMiddleware::class,
    CorsMiddleware::class,
]);
Tip: Use global middleware for cross-cutting concerns like logging and CORS. Use route-specific middleware for authorization and role checks.

Next Steps