Building a Response class — status, headers, body stream
Concept
Building a Response class encapsulates an HTTP response: status code, headers, and body. The framework constructs a Response in the controller/handler layer and the emitter sends it to the browser.
HTTP response structure:
- Status line:
HTTP/1.1 200 OK - Headers:
Content-Type: application/json\r\n... - Blank line:
\r\n - Body: response body bytes
Status code + reason phrase: Status codes have standard reason phrases (200 → OK, 404 → Not Found, 422 → Unprocessable Content). Frameworks typically provide a map of code → phrase.
Header storage: Headers are multi-value — a response can have multiple Set-Cookie headers. Store as array<string, string[]> (name → array of values). Single-value headers are stored as [name => ['value']].
Body: Can be a simple string for small responses. For streaming (large file downloads), it should be a stream/resource. For now, a string is fine.
Immutable design: Like the Request, a Response can be designed as immutable. with*() methods return new instances. This is PSR-7's design.
Convenience constructors:
Response::json(mixed $data, int $status = 200): Creates a JSON response.Response::html(string $html, int $status = 200): Creates an HTML response.Response::redirect(string $url, int $status = 302): Creates a redirect response.
Code Example
<?php
namespace Framework\Http;
class Response
{
private static array $phrases = [
200 => 'OK', 201 => 'Created', 204 => 'No Content',
301 => 'Moved Permanently', 302 => 'Found', 304 => 'Not Modified',
400 => 'Bad Request', 401 => 'Unauthorized', 403 => 'Forbidden',
404 => 'Not Found', 405 => 'Method Not Allowed', 422 => 'Unprocessable Content',
429 => 'Too Many Requests', 500 => 'Internal Server Error',
];
private array $headers = [];
public function __construct(
private string $body = '',
private int $status = 200,
array $headers = [],
) {
foreach ($headers as $name => $value) {
$this->headers[strtolower($name)][] = $value;
}
}
// Static constructors
public static function json(mixed $data, int $status = 200): static
{
return new static(
json_encode($data, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR),
$status,
['Content-Type' => 'application/json; charset=UTF-8'],
);
}
public static function html(string $html, int $status = 200): static
{
return new static($html, $status, ['Content-Type' => 'text/html; charset=UTF-8']);
}
public static function redirect(string $url, int $status = 302): static
{
return new static('', $status, ['Location' => $url]);
}
public static function noContent(): static
{
return new static('', 204);
}
// Immutable modifiers
public function withStatus(int $status): static
{
$clone = clone $this;
$clone->status = $status;
return $clone;
}
public function withHeader(string $name, string $value): static
{
$clone = clone $this;
$clone->headers[strtolower($name)] = [$value];
return $clone;
}
public function withAddedHeader(string $name, string $value): static
{
$clone = clone $this;
$clone->headers[strtolower($name)][] = $value;
return $clone;
}
// Readers
public function getStatus(): int { return $this->status; }
public function getBody(): string { return $this->body; }
public function getHeaders(): array { return $this->headers; }
public function getHeader(string $name): array { return $this->headers[strtolower($name)] ?? []; }
public function getReasonPhrase(): string { return static::$phrases[$this->status] ?? 'Unknown'; }
}