PHPackages                             joby/smol-response - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. joby/smol-response

ActiveLibrary

joby/smol-response
==================

A straightforward abstraction layer for building and rendering HTTP responses.

v1.3.0(2mo ago)0148↓50%2MITPHPPHP &gt;=8.1CI passing

Since Jan 15Pushed 2mo agoCompare

[ Source](https://github.com/joby-lol/smol-response)[ Packagist](https://packagist.org/packages/joby/smol-response)[ RSS](/packages/joby-smol-response/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (4)Dependencies (3)Versions (5)Used By (2)

smolResponse
============

[](#smolresponse)

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
------------

[](#installation)

```
composer require joby/smol-response
```

Quick Start
-----------

[](#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
---------------

[](#core-components)

### Response (`Response`)

[](#response-response)

The main response builder. Create responses with various content types and configurations.

**Factory Methods:**

- `json($data, $status = 200)` - Create JSON response
- `file($path, $status = 200)` - Create file response
- `redirect($url, $permanent = false, $preserve_method = false)` - Create redirect response

**Fluent Methods:**

```
$response = (new Response(201, 'Created'))
    ->cachePublicContent()
    ->setStatus(200);
```

### Status (`Status`)

[](#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`)

[](#cache-control-cachecontrol)

First-class cache control with support for modern caching strategies:

**Presets:**

- `publicContent($max_age = 300, $max_stale_age = 86400)` - Public HTML pages
- `publicMedia($max_age = 31536000, $max_stale_age = 31536000)` - Public static assets
- `privateContent($max_age = 300, $max_stale_age = 600)` - Private HTML pages
- `privateMedia($max_age = 31536000, $max_stale_age = 31536000)` - Private static assets
- `neverCached()` - 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`)

[](#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

[](#content-types)

All content types implement `ContentInterface` with automatic metadata handling.

#### StringContent

[](#stringcontent)

For text and HTML content:

```
// With optional filename parameter (defaults to 'page.html')
$content = new StringContent('Hello World', 'custom.html');
$response->content = $content;

// Or set filename separately
$content = new StringContent('Hello World');
$content->setFilename('page.html');
$response->content = $content;
```

#### CallbackContent

[](#callbackcontent)

For dynamic content generation with callbacks:

```
// Simple callback with optional filename parameter (defaults to 'page.html')
$content = new CallbackContent(function() {
    echo renderTemplate('page.html', ['title' => 'Welcome']);
}, 'welcome.html');

// With external data
$data = fetchDataFromDatabase();
$content = new CallbackContent(function() use ($data) {
    foreach ($data as $item) {
        echo "{$item->name}";
    }
});

// Supports any callable type
$content = new CallbackContent([$templateEngine, 'render'], 'output.html');
$response->content = $content;
```

#### JsonContent

[](#jsoncontent)

For JSON responses:

```
$content = new JsonContent(['status' => 'ok', 'items' => $items]);
$response->content = $content;
```

#### FileContent

[](#filecontent)

For serving files with automatic MIME detection:

```
// Filename defaults to basename of the file path
$content = new FileContent('/path/to/file.pdf');
$response->content = $content;

// Or override with custom filename
$content = new FileContent('/path/to/file.pdf', 'document.pdf');
$response->content = $content;
```

#### EmptyContent

[](#emptycontent)

For responses with no body:

```
$response = new Response(204, new EmptyContent());
```

Advanced Features
-----------------

[](#advanced-features)

### Range Requests

[](#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

[](#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

[](#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-Type` with charset for text content
- `Content-Length` for known-size content
- `Content-Disposition` with filename for downloads
- `Etag` from content hashes
- `Last-Modified` from file modification times
- `Accept-Ranges` for range-capable content
- `Content-Range` for partial content responses

### Fluent Response Building

[](#fluent-response-building)

Chain methods for readable response construction:

```
$response = Response::json($data)
    ->setStatus(201)
    ->cachePublicContent();

$response->headers['Location'] = '/api/resource/123';
```

### Content Metadata

[](#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
---------

[](#use-cases)

### Static File Router

[](#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

[](#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

[](#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

[](#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";
    }
}));
```

Requirements
------------

[](#requirements)

Fully tested on PHP 8.3+, static analysis for PHP 8.1+.

License
-------

[](#license)

MIT License - See [LICENSE](LICENSE) file for details.

###  Health Score

40

—

FairBetter than 88% of packages

Maintenance83

Actively maintained with recent releases

Popularity13

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~12 days

Total

4

Last Release

87d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/f4e606aa41ebfd7d82c365c6dc4ec158fb1b0bfff5f8dcbfad4ab53e3092e6a6?d=identicon)[jobyone](/maintainers/jobyone)

---

Top Contributors

[![joby-lol](https://avatars.githubusercontent.com/u/856610?v=4)](https://github.com/joby-lol "joby-lol (2 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/joby-smol-response/health.svg)

```
[![Health](https://phpackages.com/badges/joby-smol-response/health.svg)](https://phpackages.com/packages/joby-smol-response)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
