Skip to content

PHP PSR-7 HTTP Messages — Complete Guide to Request and Response Interfaces

DodaTech Updated 2026-06-28 3 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-7 standardizes HTTP request and response message interfaces, enabling framework-agnostic handling of HTTP messages through immutable value objects.

What You'll Learn

By the end of this tutorial, you'll create and manipulate PSR-7 requests, responses, URIs, and streams, understand immutability patterns, and use PSR-7 in middleware and controllers.

Why PSR-7 Matters

Before PSR-7, each framework had its own request/response objects. PSR-7 enables portable middleware, reusable HTTP libraries, and framework interoperability.

Real-World Use

A PHP library for OAuth 2.0 accepts PSR-7 RequestInterface and returns ResponseInterface, working identically with Laravel, Symfony, Slim, and any PSR-7 compatible framework.

PSR-7 Path

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

Creating Requests

PSR-7 requests are created with a Factory and modified immutably.

<?php
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\ServerRequest;
$request = new Request("POST", "https://api.example.com/users", [
    "Content-Type" => "application/json",
    "Authorization" => "Bearer token123",
], json_encode(["name" => "Alice"]));
echo $request->getMethod();
echo $request->getUri()->getHost();
$withNewHeader = $request->withHeader("X-Custom", "value");

Working with Responses

Create and modify PSR-7 responses with status codes and body.

<?php
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Psr7\Stream;
$response = new Response(
    status: 200,
    headers: ["Content-Type" => "application/json"],
    body: \GuzzleHttp\Psr7\Utils::streamFor(json_encode(["status" => "ok"])),
);
echo $response->getStatusCode();
echo $response->getReasonPhrase();
$json = json_decode($response->getBody()->__toString(), true);
$errorResponse = $response->withStatus(404, "Not Found");

Stream Interface

PSR-7 StreamInterface wraps PHP streams for request/response bodies.

<?php
use GuzzleHttp\Psr7\Stream;
$stream = \GuzzleHttp\Psr7\Utils::streamFor("Hello World");
echo $stream->getSize();
echo $stream->tell();
$stream->rewind();
echo $stream->read(5);
echo $stream->getContents();
$stream->close();

URI Interface

PSR-7 UriInterface provides immutable URI manipulation.

<?php
use GuzzleHttp\Psr7\Uri;
$uri = new Uri("https://user:pass@api.example.com:8080/path/to/resource?query=value#fragment");
echo $uri->getScheme();
echo $uri->getHost();
echo $uri->getPort();
echo $uri->getPath();
echo $uri->getQuery();
$newUri = $uri->withHost("admin.example.com")->withPath("/dashboard");

Uploaded Files

PSR-7 handles file uploads through UploadedFileInterface.

<?php
use GuzzleHttp\Psr7\UploadedFile;
$uploadedFile = new UploadedFile(
    "/tmp/php/uploaded_file",
    1024,
    UPLOAD_ERR_OK,
    "document.pdf",
    "application/pdf"
);
echo $uploadedFile->getClientFilename();
echo $uploadedFile->getClientMediaType();
echo $uploadedFile->getSize();
$uploadedFile->moveTo("/storage/documents/doc.pdf");

Common Mistakes

1. Forgetting PSR-7 Objects Are Immutable

Methods like withHeader() return new instances. The original object is not modified.

2. Reading Body Multiple Times

Streams can be read once. Use getContents() or rewind() before reading again.

3. Not Using Factory for Uploaded Files

Create UploadedFile instances from $_FILES using a factory, not manually.

4. Ignoring Stream Size

getSize() returns null for streams without a known size (e.g., network streams).

5. Modifying URI Components in Place

URI is immutable. Methods like withHost return new instances. Chain them together.

Practice Questions

1. What does ResponseInterface::withStatus return?

A new ResponseInterface instance with the modified status code.

2. How do you read a request body as a string?

$request->getBody()->getContents() or $request->getBody()->__toString().

3. What is the difference between getHeaders and getHeader?

getHeaders returns all headers as array. getHeader(name) returns one header as array of strings.

4. Are PSR-7 objects serializable?

Not directly. Use __toString() on body and serialize header arrays manually.

5. Challenge: Create a PSR-7 request validation middleware.

<?php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class ValidationMiddleware implements MiddlewareInterface {
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
        if ($request->getMethod() === "POST") {
            $body = json_decode((string) $request->getBody(), true);
            if (empty($body["name"])) {
                $response = new \GuzzleHttp\Psr7\Response(400);
                $response->getBody()->write(json_encode(["error" => "Name required"]));
                return $response;
            }
        }
        return $handler->handle($request);
    }
}

FAQ

Which PSR-7 implementation should I use?

Guzzle PSR-7, nyholm/psr7, or slim/psr7 are popular. Guzzle's is the most widely adopted.

Can I use PSR-7 without a framework?

Yes. PSR-7 objects can be used standalone. They need a server request factory for HTTP input.

How do I send a PSR-7 response?

Use a PSR-18 HTTP client or emit the response using a framework emitter.

What is the difference between Request and ServerRequest?

ServerRequest extends Request with server parameters, cookie, query, and parsed body attributes.

Are PSR-7 streams seekable?

Not always. File streams are seekable. Network or php://input streams may not be.

Mini Project: PSR-7 Request Logger Middleware

Build a middleware that logs PSR-7 request details.

<?php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class RequestLogger implements MiddlewareInterface {
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
        $log = sprintf(
            "[%s] %s %s - %s",
            date("Y-m-d H:i:s"),
            $request->getMethod(),
            (string) $request->getUri(),
            $request->getHeaderLine("User-Agent")
        );
        file_put_contents("/var/log/requests.log", $log . PHP_EOL, FILE_APPEND);
        return $handler->handle($request);
    }
}

What's Next

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

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro