joby / smol-response
A straightforward abstraction layer for building and rendering HTTP responses.
Installs: 4
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/joby/smol-response
Requires
- php: >=8.4
- joby/smol-url: ^1.0@beta
Requires (Dev)
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^12
This package is auto-updated.
Last update: 2026-01-07 18:22:07 UTC
README
A straightforward, type-safe HTTP response builder for PHP. Build and send HTTP responses with proper headers, cache control, content types, and status codes. Designed to minimize footguns, maximize productivity, and provide useful hinting and IntelliSense features in your IDE.
Features:
- Type-safe response building with fluent interfaces
- First-class cache control headers
- Automatic content type detection and charset handling
- Range request support for efficient streaming
- Built-in content types: strings, JSON, files, callbacks, and empty responses
- Requires PHP 8.4+
Installation
composer require joby/smol-response
Quick Start
use Joby\Smol\Response\Response; // Simple text response $response = new Response(200, 'Hello World'); // JSON response $response = Response::json(['status' => 'ok', 'data' => $results]); // File download $response = Response::file('/path/to/file.pdf'); // Redirect $response = Response::redirect('https://example.com', permanent: true); // Render the response $renderer = new \Joby\Smol\Response\Renderer(); $renderer->render($response);
Core Components
Response (Response)
The main response builder. Create responses with various content types and configurations.
Factory Methods:
json($data, $status = 200)- Create JSON responsefile($path, $status = 200)- Create file responseredirect($url, $permanent = false, $preserve_method = false)- Create redirect response
Fluent Methods:
$response = (new Response(201, 'Created')) ->cachePublicContent() ->setStatus(200);
Status (Status)
Type-safe HTTP status codes with standard reason phrases:
$status = new Status(404); echo $status->code; // 404 echo $status->reason_phrase; // "Not Found"
Cache Control (CacheControl)
First-class cache control with support for modern caching strategies:
Presets:
publicContent($max_age = 300, $max_stale_age = 86400)- Public HTML pagespublicMedia($max_age = 31536000, $max_stale_age = 31536000)- Public static assetsprivateContent($max_age = 300, $max_stale_age = 600)- Private HTML pagesprivateMedia($max_age = 31536000, $max_stale_age = 31536000)- Private static assetsneverCached()- No caching (CSRF forms, CAPTCHAs)
$response->cache = CacheControl::publicMedia(); // Renders: public, max-age=31536000, s-maxage=31536000, // stale-while-revalidate=31536000, stale-if-error=31536000 $response->cachePublicContent(); // Fluent helper method
Headers (Headers)
Normalized header collection with automatic header name formatting:
$response->headers['content-type'] = 'application/json'; $response->headers['X-Custom-Header'] = 'value'; // Headers are automatically normalized to proper case foreach ($response->headers as $name => $value) { echo "$name: $value\n"; // Content-Type: application/json // X-Custom-Header: value }
Content Types
All content types implement ContentInterface with automatic metadata handling.
StringContent
For text and HTML content:
$content = new StringContent('<h1>Hello World</h1>'); $content->setFilename('page.html'); $response->content = $content;
CallbackContent
For dynamic content generation with callbacks:
// Simple callback $content = new CallbackContent(function() { echo renderTemplate('page.html', ['title' => 'Welcome']); }); // With external data $data = fetchDataFromDatabase(); $content = new CallbackContent(function() use ($data) { foreach ($data as $item) { echo "<li>{$item->name}</li>"; } }); // Supports any callable type $content = new CallbackContent([$templateEngine, 'render']); $content->setFilename('output.html'); $response->content = $content;
JsonContent
For JSON responses:
$content = new JsonContent(['status' => 'ok', 'items' => $items]); $response->content = $content;
FileContent
For serving files with automatic MIME detection:
$content = new FileContent('/path/to/file.pdf'); $response->content = $content;
EmptyContent
For responses with no body:
$response = new Response(204, new EmptyContent());
Advanced Features
Range Requests
Efficiently serve partial content for video streaming and resumable downloads:
// Content automatically supports range requests $content = new FileContent('/path/to/video.mp4'); // Apply a range manually $ranged = new AppliedRangeContent($content, 0, 1024); $response->content = $ranged; // Renderer automatically sets 206 status and Content-Range header
Custom Content Types
Create your own content types by implementing ContentInterface or extending AbstractContent:
class TemplateContent extends AbstractContent { public function __construct(private string $template, private array $data) { $this->filename = 'page.html'; } public function render(): void { echo $this->engine->render($this->template, $this->data); } public function size(): int|null { return null; // Unknown until rendered } }
Response Rendering
The Renderer handles all HTTP output:
$renderer = new Renderer(); $renderer->render($response); // Or test header generation without sending $headers = $renderer->buildHeaders($response);
Automatic Header Generation:
Content-Typewith charset for text contentContent-Lengthfor known-size contentContent-Dispositionwith filename for downloadsETagfrom content hashesLast-Modifiedfrom file modification timesAccept-Rangesfor range-capable contentContent-Rangefor partial content responses
Fluent Response Building
Chain methods for readable response construction:
$response = Response::json($data) ->setStatus(201) ->cachePublicContent(); $response->headers['Location'] = '/api/resource/123';
Content Metadata
All content types provide rich metadata:
echo $content->filename(); // 'document.pdf' echo $content->mime(); // 'application/pdf' echo $content->contentType(); // 'application/pdf' echo $content->size(); // File size in bytes echo $content->etag(); // MD5 hash for caching echo $content->lastModified(); // DateTime of last modification
Use Cases
Static File Router
Build a simple static file server with aggressive caching:
$path = '/var/www/public' . $_SERVER['REQUEST_URI']; if (file_exists($path)) { $response = Response::file($path)->cachePublicMedia(); (new Renderer())->render($response); }
API Responses
Consistent JSON API responses:
function apiSuccess($data, $code = 200) { return Response::json(['success' => true, 'data' => $data], $code) ->cacheNever(); } function apiError($message, $code = 400) { return Response::json(['success' => false, 'error' => $message], $code) ->cacheNever(); }
Content Transformations
Transform content before serving:
$djot = file_get_contents('content.djot'); $html = renderDjotToHtml($djot); $response = (new Response(200, $html)) ->cachePublicContent(); $response->headers['Content-Type'] = 'text/html; charset=UTF-8';
Dynamic Content Generation
Use callbacks for complex rendering logic:
// Template rendering with caching $response = new Response(200, new CallbackContent(function() use ($templateEngine, $data) { echo $templateEngine->render('dashboard.html', $data); })); $response->cachePublicContent(); // Streaming output $response = new Response(200, new CallbackContent(function() { foreach (generateLargeDataset() as $chunk) { echo json_encode($chunk) . "\n"; } }));