PHP PSR-7 HTTP Messages — Complete Guide to Request and Response Interfaces
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
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