Skip to content

PHP PSR-15 Middleware — Complete Guide to HTTP Middleware Interfaces

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about PHP PSR. We cover key concepts, practical examples, and best practices to help you master this topic.

PHP PSR-15 defines standard interfaces for HTTP middleware and request handlers, enabling reusable request processing pipelines that work across any PSR-7 compatible framework.

What You'll Learn

By the end of this tutorial, you'll implement PSR-15 middleware, build middleware pipelines, handle errors in middleware, create reusable middleware components, and integrate with frameworks.

Why PSR-15 Matters

PSR-15 standardizes middleware, the most common pattern for HTTP request processing. Middleware enables cross-cutting concerns like authentication, logging, and CORS in reusable packages.

Real-World Use

A Slim application uses PSR-15 middleware for request logging, JWT authentication, CORS headers, Rate Limiting, and JSON body Parsing, all implemented as standalone middleware classes.

PSR-15 Path

flowchart LR
  A[PSR-14 Events] --> B[PSR-15 Middleware]
  B --> C[PSR-17 HTTP Factory]
  C --> D[PSR-11 Container]
  D --> E[PHP Middleware Slim]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

MiddlewareInterface

PSR-15 middleware receives a request and a handler, processes the request, and returns a response.

<?php
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);
        return $response->withHeader("Content-Type", "application/json");
    }
}

RequestHandlerInterface

A request handler processes the request and returns a response. In a pipeline, it represents the next middleware or the final application handler.

<?php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class AppHandler implements RequestHandlerInterface {
    public function __construct(
        private array $routes
    ) {}
    public function handle(ServerRequestInterface $request): ResponseInterface {
        $path = $request->getUri()->getPath();
        $handler = $this->routes[$path] ?? null;
        if (!$handler) {
            $response = new \GuzzleHttp\Psr7\Response(404);
            $response->getBody()->write("Not Found");
            return $response;
        }
        return $handler($request);
    }
}

Middleware Pipeline

Create a pipeline that chains multiple middleware together.

<?php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class MiddlewarePipeline implements RequestHandlerInterface {
    private array $middleware;
    private RequestHandlerInterface $fallback;
    public function __construct(array $middleware, RequestHandlerInterface $fallback) {
        $this->middleware = $middleware;
        $this->fallback = $fallback;
    }
    public function handle(ServerRequestInterface $request): ResponseInterface {
        if (empty($this->middleware)) {
            return $this->fallback->handle($request);
        }
        $middleware = array_shift($this->middleware);
        return $middleware->process($request, $this);
    }
}

Authentication Middleware

Validate JWT tokens in middleware.

<?php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class JwtAuthMiddleware implements MiddlewareInterface {
    public function __construct(
        private string $secret
    ) {}
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
        $auth = $request->getHeaderLine("Authorization");
        if (!str_starts_with($auth, "Bearer ")) {
            $response = new \GuzzleHttp\Psr7\Response(401);
            $response->getBody()->write("Unauthorized");
            return $response;
        }
        $token = substr($auth, 7);
        $payload = json_decode(base64_decode(explode(".", $token)[1]), true);
        $request = $request->withAttribute("user_id", $payload["sub"]);
        return $handler->handle($request);
    }
}

Error Handling Middleware

Catch exceptions from inner middleware and return appropriate responses.

<?php
class ErrorHandlingMiddleware implements MiddlewareInterface {
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
        try {
            return $handler->handle($request);
        } catch (\InvalidArgumentException $e) {
            $response = new \GuzzleHttp\Psr7\Response(400);
            $response->getBody()->write(json_encode(["error" => $e->getMessage()]));
            return $response->withHeader("Content-Type", "application/json");
        } catch (\Throwable $e) {
            $response = new \GuzzleHttp\Psr7\Response(500);
            $response->getBody()->write("Internal Server Error");
            return $response;
        }
    }
}

Common Mistakes

1. Not Calling $handler->handle()

Middleware that does not call the handler breaks the pipeline. Always call $handler->handle() unless returning early.

2. Modifying Request After Passing to Handler

The request object should be immutable. Use withAttribute() to add data for downstream handlers.

3. Returning Non-PSR-7 Responses

Always return a PSR-7 ResponseInterface. Returning strings or arrays breaks the pipeline.

4. Throwing Exceptions in Middleware

Catch exceptions and return error responses. Uncaught exceptions leave the pipeline incomplete.

5. Heavy Processing in Middleware

Middleware runs on every request. Keep it fast. Offload heavy work to the application handler.

Practice Questions

1. What does MiddlewareInterface::Process receive?

A ServerRequestInterface and a RequestHandlerInterface representing the next middleware or handler.

2. What must middleware return?

A ResponseInterface object. The response flows back through the middleware chain.

3. How do you pass data from middleware to the application?

Use $request->withAttribute(name, value) to attach data to the request object.

4. What happens if middleware does not call $handler->handle()?

The inner middleware and application handler never execute. The middleware short-circuits the pipeline.

5. Challenge: Create a rate-limiting middleware using PSR-15.

<?php
class RateLimitMiddleware implements MiddlewareInterface {
    private array $requests = [];
    public function __construct(
        private int $maxRequests = 100,
        private int $windowMs = 3600000
    ) {}
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
        $ip = $request->getServerParams()["REMOTE_ADDR"] ?? "unknown";
        $this->cleanup();
        $this->requests[$ip][] = microtime(true);
        if (count($this->requests[$ip]) > $this->maxRequests) {
            $response = new \GuzzleHttp\Psr7\Response(429);
            $response->getBody()->write("Rate limit exceeded");
            return $response;
        }
        return $handler->handle($request);
    }
    private function cleanup(): void {
        $cutoff = microtime(true) - $this->windowMs;
        foreach ($this->requests as $ip => $times) {
            $this->requests[$ip] = array_filter($times, fn($t) => $t > $cutoff);
        }
    }
}

FAQ

What is the difference between PSR-15 and PSR-7?

PSR-7 defines HTTP message objects. PSR-15 defines how to process those messages through middleware.

Can I use PSR-15 with Laravel?

Laravel has its own middleware system. A bridge can adapt PSR-15 middleware to Laravel.

What is a RequestHandler?

The next component in the pipeline. It could be another middleware or the final application handler.

How do I test PSR-15 middleware?

Create mock request and handler. Assert the response status, headers, and body.

Is PSR-15 compatible with ReactPHP or async PHP?

PSR-15 is synchronous. Async frameworks need their own middleware abstractions.

Mini Project: CORS Middleware

Build a reusable CORS middleware implementing PSR-15.

<?php
class CorsMiddleware implements MiddlewareInterface {
    public function __construct(
        private array $allowedOrigins = ["*"],
        private array $allowedMethods = ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
        private array $allowedHeaders = ["Content-Type", "Authorization"],
    ) {}
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
        if ($request->getMethod() === "OPTIONS") {
            $response = new \GuzzleHttp\Psr7\Response(204);
        } else {
            $response = $handler->handle($request);
        }
        $origin = $request->getHeaderLine("Origin");
        if (in_array("*", $this->allowedOrigins) || in_array($origin, $this->allowedOrigins)) {
            $response = $response
                ->withHeader("Access-Control-Allow-Origin", $origin ?: "*")
                ->withHeader("Access-Control-Allow-Methods", implode(", ", $this->allowedMethods))
                ->withHeader("Access-Control-Allow-Headers", implode(", ", $this->allowedHeaders))
                ->withHeader("Access-Control-Max-Age", "86400");
        }
        return $response;
    }
}

What's Next

PHP PSR-17 HTTP Factory PHP Middleware Slim PHP PSR-11 Container

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro