skip to content

In PHP, what is PSR-7, and which interfaces does it define to represent HTTP requests, responses and their parts?

level: juniorimportance: should knowfreq 42%

answer

  1. a PHP-FIG standard, not a library
  2. MessageInterface is the common parent
  3. RequestInterface vs ServerRequestInterface
  4. StreamInterface for bodies, UriInterface for URIs
  5. UploadedFileInterface normalises $_FILES

basics

~10 s

PSR-7 is the PHP-FIG standard for HTTP messages: interfaces in Psr\Http\Message for requests, server requests, responses, bodies (StreamInterface), URIs and uploaded files. Code typed against them works with any conforming implementation.

solid answer

~40 s

PSR-7 is a PHP-FIG recommendation that defines interfaces, not an implementation, published as the `psr/http-message` package in the `Psr\Http\Message` namespace. `MessageInterface` holds what requests and responses share: protocol version, headers and a body. `RequestInterface` adds the method, the request target and a `UriInterface`; `ServerRequestInterface` extends it with what PHP's SAPI provides on the server side: server params, cookies, query params, the parsed body, uploaded files and attributes. `ResponseInterface` adds the status code and reason phrase. The body is a `StreamInterface`, and each uploaded file is an `UploadedFileInterface`. Messages and URIs are immutable value objects changed through `with*()` methods. The payoff is interoperability: a library typed against these interfaces works with whichever implementation the application installs.

code

php · 14 lines
php
<?php
declare(strict_types=1);

use Psr\Http\Message\ServerRequestInterface;

function describe(ServerRequestInterface $request): string
{
    $method = $request->getMethod();
    $path   = $request->getUri()->getPath();
    $accept = $request->getHeaderLine('accept'); // case-insensitive lookup
    $page   = $request->getQueryParams()['page'] ?? '1';

    return sprintf('%s %s (accept: %s, page %s)', $method, $path, $accept, $page);
}

go deeper

for a junior

Name PSR-7 as the shared HTTP message interfaces and list the main ones: request, server request, response, stream, URI and uploaded file. Knowing it is interfaces only is the first check.

for a middle

Explain why ServerRequestInterface extends RequestInterface, how it wraps the superglobals, and how getHeader() and getHeaderLine() differ, including their empty results for a missing header.

for a senior

Show that you type library code against the PSR-7 interfaces rather than an implementation, and know the untyped 1.0, typed 1.1 and 2.0 package lines when you set Composer constraints.

for a principal

Weigh standardising a codebase on PSR-7 messages against a framework's own request objects: portability and testability versus the convenience helpers and ecosystem a framework layer adds.

## What PSR-7 is **PSR-7** is a **PHP Standards Recommendation** accepted by PHP-FIG, the group of PHP project maintainers that publishes shared interfaces. It defines how HTTP messages are represented as PHP objects. It ships as the Composer package `psr/http-message`, which contains **interfaces only**: no class you can instantiate, no parser and no emitter. You install a separate implementation package that provides concrete classes, and your own code types its parameters and return values against the interfaces. Before PSR-7, every framework and HTTP client had its own request and response classes, so a piece of middleware, an OAuth library or an API SDK had to be written once per framework. With a shared set of interfaces, one package can accept and return messages from any conforming implementation. ## The interfaces All of them live in the `Psr\Http\Message` namespace. | Interface | Models | Key methods | |---|---|---| | `MessageInterface` | what requests and responses share | `getProtocolVersion()`, `getHeaders()`, `getHeader()`, `getHeaderLine()`, `getBody()`, `withHeader()`, `withBody()` | | `RequestInterface` | an outgoing, client-side request | `getMethod()`, `getRequestTarget()`, `getUri()`, `withMethod()`, `withUri()` | | `ServerRequestInterface` | an incoming request as the server sees it | `getServerParams()`, `getCookieParams()`, `getQueryParams()`, `getParsedBody()`, `getUploadedFiles()`, `getAttribute()` | | `ResponseInterface` | a response | `getStatusCode()`, `getReasonPhrase()`, `withStatus()` | | `StreamInterface` | a message body | `read()`, `write()`, `getContents()`, `rewind()`, `isSeekable()` | | `UriInterface` | a URI | `getScheme()`, `getHost()`, `getPath()`, `getQuery()`, `withPath()` | | `UploadedFileInterface` | one uploaded file | `getStream()`, `moveTo()`, `getError()`, `getClientFilename()` | `RequestInterface` and `ResponseInterface` both extend `MessageInterface`, and `ServerRequestInterface` extends `RequestInterface`. ## Why a server request is separate A request that a client builds and sends has a method, a URI, headers and a body. A request that arrives at a PHP application carries more: PHP has already decoded parts of it into the superglobals `$_SERVER`, `$_GET`, `$_POST`, `$_COOKIE` and `$_FILES`. `ServerRequestInterface` wraps that data behind methods, which has two effects: - code that reads the request no longer touches global state, so it can be unit-tested by building a request object; - `getUploadedFiles()` returns a tree of `UploadedFileInterface` objects, which fixes the awkward nested layout `$_FILES` produces for array inputs. It also adds **attributes**, a bag for values derived from the request (a matched route parameter, an authenticated user) that one piece of code can attach for the next one to read. ## Headers Header names are **case-insensitive** on lookup, but an implementation must preserve the original case in `getHeaders()`. Each header can hold several values: - `getHeader('Accept')` returns an array of values, or an empty array if the header is missing; - `getHeaderLine('Accept')` returns the values joined with a comma, or an empty string if the header is missing; - `withAddedHeader()` appends a value, `withHeader()` replaces all values, `withoutHeader()` removes the header. Some headers, notably `Set-Cookie`, cannot be joined with commas, so the specification says to read them with `getHeader()`. ## Immutability in one paragraph Messages, URIs and uploaded-file objects are **value objects**: every method that would change state is named `with...()` or `without...()` and returns a message carrying the change, leaving the original untouched. The one exception is `StreamInterface`, which wraps a PHP stream and therefore cannot be made immutable. ## Related standards built on PSR-7 1. **PSR-15** defines `RequestHandlerInterface` and `MiddlewareInterface` for server-side request processing. 2. **PSR-17** defines factories such as `ResponseFactoryInterface` and `StreamFactoryInterface`, so a library can create messages without naming an implementation class. 3. **PSR-18** defines `ClientInterface::sendRequest()` for sending a request and receiving a response. ## Versions of the package The original `psr/http-message` 1.0 declared no parameter or return types. Release 1.1 added parameter types and 2.0 added return types; the methods and their meaning did not change. An implementation declares which of these it supports, and Composer picks compatible versions. ## Misconceptions interviewers listen for - **"PSR-7 is a framework component."** It is a set of interfaces; frameworks and libraries implement or consume them. - **"The request object has setters."** There are none; every change goes through `with*()` and yields a new instance. - **"`RequestInterface` is what a controller receives."** Server-side code receives a `ServerRequestInterface`, which adds the SAPI-derived data; `RequestInterface` alone is what an HTTP client sends. - **"Headers are a plain associative array."** Lookups ignore case and each header maps to a list of values, which is why two getters exist. A good junior answer names the package, the main interfaces and the idea of coding against them; everything else builds on that.

  • Does installing psr/http-message give you a Request class you can instantiate?
    No. `psr/http-message` contains only interfaces. To create objects you install an implementation package that provides concrete classes, and ideally you create them through PSR-17 factory interfaces, so your code still names only standard interfaces.
  • In PSR-7, what do getHeader() and getHeaderLine() return for a header the message does not have?
    `getHeader()` returns an empty array and `getHeaderLine()` returns an empty string; neither returns null or throws. Use `hasHeader()` when you need to tell a missing header apart from one that is present with an empty value.

saying these in an interview costs you the question

  • Believes PSR-7 is a library with concrete Request and Response classes.
  • Thinks RequestInterface already exposes query params, cookies and uploaded files.
  • Assumes header lookup in PSR-7 is case-sensitive.
  • Says getHeaderLine() returns null for a missing header.
  • Calls PSR-7 messages mutable objects with setters.