0

PSR-7 — HTTP Messages (Request/Response interfaces)

Intermediate5 min read·php-12-009
psrframeworkinterview

Concept

PSR-7 defines interfaces for HTTP messages: requests, responses, URIs, streams, and uploaded files. It's the foundation for framework-agnostic HTTP handling in PHP. Guzzle HTTP client, Slim Framework, Diactoros, and middleware libraries all implement PSR-7.

Immutability: PSR-7 messages are immutable value objects. Every with*() method returns a new instance: $response->withStatus(200) does NOT modify $response — it returns a new ResponseInterface. This prevents action-at-a-distance bugs where modifying a request in one place unexpectedly affects another.

Key interfaces:

  • MessageInterface: Base for request/response. Headers, protocol version, body (as StreamInterface).
  • RequestInterface extends MessageInterface: Client-side request. Method, URI, request target.
  • ServerRequestInterface extends RequestInterface: Server-side. $_SERVER params, cookies, parsed body, uploaded files, attributes.
  • ResponseInterface extends MessageInterface: Status code, reason phrase, body.
  • UriInterface: URI with individual components — scheme, host, port, path, query, fragment.
  • StreamInterface: Abstraction over a data stream. Can be a file, string, socket. Has read(), write(), seek(), getContents().
  • UploadedFileInterface: Uploaded file abstraction.

PSR-17: HTTP factories — creates PSR-7 objects. RequestFactoryInterface, ResponseFactoryInterface, StreamFactoryInterface, etc.

PSR-18: HTTP client interface. ClientInterface with a single sendRequest(RequestInterface): ResponseInterface method. Guzzle implements it. Lets you swap HTTP clients without changing application code.

Code Example

php
<?php
declare(strict_types=1);
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

// PSR-7 request is immutable — with* methods return new instances
function addAuthHeader(RequestInterface $request, string $token): RequestInterface
{
    // Returns NEW request — original unchanged
    return $request->withHeader('Authorization', 'Bearer ' . $token);
}

// Reading from a PSR-7 server request
function handleRequest(ServerRequestInterface $request): ResponseInterface
{
    $method = $request->getMethod(); // "GET", "POST", etc.
    $path   = $request->getUri()->getPath(); // "/api/users"
    $query  = $request->getQueryParams(); // ['page' => '2']
    $body   = $request->getParsedBody(); // decoded POST body
    $cookie = $request->getCookieParams();
    $header = $request->getHeaderLine('Content-Type');

    // Attributes set by middleware
    $userId = $request->getAttribute('user_id');
}

// PSR-7 response
use Laminas\Diactoros\Response\JsonResponse;
use Laminas\Diactoros\Response;
use Nyholm\Psr7\Factory\Psr17Factory;

$factory = new Psr17Factory();
$response = $factory->createResponse(200)
    ->withHeader('Content-Type', 'application/json');

$stream = $factory->createStream(json_encode(['status' => 'ok']));
$response = $response->withBody($stream);

// PSR-18 HTTP client (Guzzle)
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Request;

$client = new Client(['base_uri' => 'https://api.example.com']);
$request = new Request('GET', '/users/1', ['Accept' => 'application/json']);
$response = $client->sendRequest($request); // PSR-18
$data = json_decode((string) $response->getBody(), true);