Luminova Framework

PHP Luminova: HTTP File Downloader

Last updated: 2026-04-10 10:12:12

Download utility for sending files and generated content as downloadable responses. Supports file paths, streams, resources, and raw strings, with built-in handling for range requests, caching, server offloading via Apache X-Sendfile and Nginx X-Accel-Redirect.

The HTTP File Downloader is a practical utility for sending files to the browser without juggling headers, streams, and edge cases yourself.

It gives you one consistent way to handle downloads, from a file on disk, an already opened stream, or generated content in memory. Instead of writing different code for each case, you use the same interface to handle downloads.

It handles:

  • Correct download headers (so the browser actually downloads instead of displaying)
  • Partial downloads (resume support via HTTP 206)
  • Cache validation (ETag and Last-Modified)
  • Efficient streaming to avoid memory issues

It also supports server-level offloading, instead of PHP doing all the work (which is slow for large files), you can hand the job over to the web server using:

  • Apache X-Sendfile
  • Nginx X-Accel-Redirect

Download Source

The downloader accepts multiple download source:

  • Filesystem path – the standard case for serving stored files
  • PHP resource – useful if the file is already opened (fopen)
  • PSR-7 StreamInterface – fits into middleware or modern HTTP stacks
  • SplFileObject – object-oriented file handling
  • Raw string – for generated content like CSV, JSON, or exports

Usages

This section shows how to send files or content to the browser using the Downloader.If you’ve ever clicked a “Download” button on a website, this is the code behind it.


Basic file download

The simplest case: send a file to the browser.

use Luminova\Http\Downloader;

$dl = new Downloader('/var/storage/report.pdf', 'Q3-Report.pdf');

$dl->download();

// Optional: remove the file after sending (useful for temp files)
$dl->deleteSourceFile();

// Always close to release resources
$dl->close();

Throttled streaming (rate limiting)

Useful when sending large files and you don’t want to:

  • overload the server
  • or saturate the user’s connection
// 16 KB per chunk, 5 ms delay between chunks
$dl->download(Downloader::X_DOWNLOAD, 16384, 5000);
$dl->close();

Without throttling, PHP will try to push everything as fast as possible.That sounds good — until your server starts struggling under load.


Server Offloading

Instead of PHP doing all the work, let the web server handle it.This is faster, more stable, and uses fewer resources (Recommended for large files).


Apache X-Sendfile

PHP sends only headers while Apache reads and streams the file.

$dl = new Downloader('/var/storage/large-file.iso', 'large-file.iso');

$dl->download(Downloader::X_SENDFILE);
$dl->close();

Nginx X-Accel-Redirect

This is the Nginx version of offloading.

use Luminova\Http\Downloader;
use function Luminova\Funcs\root;

$filepath = root('/writeable/storages/videos/', 'tour.mp4');
$storages = root('/writeable/storages/');
$redirect = '/protected/';

$dl = new Downloader($filepath, 'tour.mp4');

// Map real path → internal Nginx path
$dl->setAccelPaths($storages, $redirect);

$dl->download(Downloader::X_ACCEL);
$dl->close();

Important:

Nginx does not accept real file paths,it only accepts an internal URL

So you map:

/var/www/.../writeable/storages/videos/tour.mp4
→ /protected/videos/tour.mp4

Nginx configuration example

The internal; prevents direct access from the browser, only your app can trigger downloads

location /protected/ {
    internal;
    alias /var/www/example.com/writeable/storages/;
}

Working with Streams

PSR-7 stream

use Luminova\Http\Message\Stream;

$stream = Stream::from('/tmp/export.csv', 'rb');

$dl = new Downloader($stream, 'export.csv', [
    'Cache-Control' => 'no-store'
]);

$dl->download();
$dl->close();

Raw string content

Good for generated files (CSV, JSON, logs, etc.), No temp file needed. Everything is sent directly from memory.

$csv = "id,name\n1,Alice\n2,Bob\n";

$dl = new Downloader($csv, 'users.csv', [
    'Content-Type' => 'text/csv'
]);

$dl->download();
$dl->close();

Resource or SplFileObject

To reuse already opened handlers and avoid reopening files.

$fp = fopen('/var/storage/data.bin', 'rb');
$dl = new Downloader($fp, 'data.bin');

$dl->download();
$dl->close();

$fo = new SplFileObject('/var/storage/data.bin', 'rb');
$dl = new Downloader($fo, 'data.bin');

$dl->download();
$dl->close();

Note:

Calling close() may also close the underlying resource.


Download as a Response (PSR-7)

Instead of sending output immediately, you can build a response.

use Luminova\Http\Downloader;
use Luminova\Http\Header;

$dl = new Downloader('/var/storage/archive.zip', 'archive.zip');

$response = $dl->getResponse();

// Send headers + status
Header::send($response->getHeaders(), status: $response->getStatusCode());

// Clear buffers before streaming
Header::clearOutputBuffers();

// Send body
$response->getBody()->send();

// Optional: inspect range info (resume support)
$info = $dl->getInfo();

$dl->close();

Useful when you’re inside middleware or want full control over response handling


Static Proxy Methods

Shortcuts when you don’t want to manually create an instance.

use Luminova\Http\Downloader;

// Direct streaming
Downloader::send('/var/storage/archive.zip', 'archive.zip');

// Same as send(), but suppress errors
Downloader::trySend('/var/storage/archive.zip', 'archive.zip');

// Apache offload (fallback to PHP)
Downloader::sendFile('/var/storage/archive.zip', 'archive.zip');

// Nginx offload (fallback to PHP)
Downloader::sendAccel('/var/storage/archive.zip', 'archive.zip');

// Build response only (no output)
$response = Downloader::response('/var/storage/archive.zip', 'archive.zip');

Quick guidance:

  • Use send() → normal usage
  • Use sendFile() / sendAccel() → production (large files)
  • Use response() → middleware or API-style flow

Class Definition

  • Class namespace: \Luminova\Http\Downloader

Constants

ConstantValueDescription
Downloader::X_DOWNLOAD1PHP streams content directly (default)
Downloader::X_SENDFILE2Apache / Lighttpd X-Sendfile offload
Downloader::X_ACCEL3Nginx X-Accel-Redirect offload

Properties

accelRealBase

Real filesystem base path stripped when building X-Accel-Redirect URIs.

protected string $accelRealBase

Set via setAccelPaths()


accelInternalPrefix

Nginx internal location prefix used for X-Accel-Redirect URIs.

protected string $accelInternalPrefix

Set via setAccelPaths()


Methods

constructor

Initialize a new Downloader instance.

Accepts a file path, an open PHP resource, a PSR-7 StreamInterface, ora raw string of file contents as the download source.

public __construct(
    Psr\Http\Message\StreamInterface|SplFileObject|resource|string $source,
    ?string $filename = null,
    array<string,mixed> $headers = [],
    ?string $etag = null
): mixed

Parameters:

ParameterTypeDescription
$sourceStreamInterface|SplFileObject|resource|stringFile path, open resource,
PSR-7 stream, or raw content.
$filenamestring|nullFilename shown in the browser download dialog.
Falls back to the basename of the file path, or 'file_download'.
$headersarray<string,mixed>Additional HTTP headers merged into the response.
$etagstring|nullETag string for cache validation.
Falls back to $headers['ETag'] when omitted.

Throws:


__callStatic

Static proxy that creates a Downloader instance and immediately dispatchesthe requested action.

The $arguments (forwarded to the constructor) and immediately dispatches the action named by $method.

public static __callStatic(string $method, array $arguments): mixed

Parameters:

ParameterTypeDescription
$methodstringProxy method name.
$argumentsarrayConstructor arguments forwarded to __construct.

Throws:

Note The static proxy does not expose setAccelPaths(). Use the instance API when Nginx offloading requires path configuration.


setAccelPaths

Configure the real-to-internal path mapping used when serving files via Nginx's X-Accel-Redirect.

public setAccelPaths(string $realBasePath, string $internalPrefix = '/protected/'): static

Parameters:

ParameterTypeDescription
$realBasePathstringAbsolute filesystem path that should be stripped from the source path.
(e.g. '/var/www/storage')
$internalPrefixstringNginx internal location prefix that replaces the stripped portion.
(e.g. '/protected/')

Return Value:

static - Returns the current instance for fluent chaining.


is

Check whether the resolved source type matches the given type string.

Possible type values: 'file', 'resource', 'stream', 'contents', 'fileObject'.

public is(string $type): bool

Parameters:

ParameterTypeDescription
$typestringType string to test against.

Return Value:

bool - Return true if type matches $source, otherwise false.


isPartial

Returns true when the last download served a partial (HTTP 206) response.

public isPartial(): bool

Return Value:

bool - Return true if download is partial, otherwise false.


getType

Resolve the source type, optionally re-detecting it when not yet set.

public getType(): string|false

Return Value:

string|false - The type string, or false when detection fails.


getInfo

Return metadata set during the last download() or getResponse() call.

Keys: offset (int), length (int), status (int HTTP status code).

public getInfo(): array<string,int>

Return Value:

array<string,int> - Return associative array of download information.


getFilename

Return the filename that will be (or was) sent to the client.

public getFilename(): ?string

Return Value:

string|null - Return download file name or null if not available.


getMime

Return the resolved MIME type of the download source.

public getMime(): ?string

Return Value:

string|null - Return download MIME type.


getETag

Return the ETag string used for cache validation, or null if none.

public getETag(): ?string

Return Value:

string|null - Return download ETag value or null if not available.


getSize

Return the resolved content length in bytes.

public getSize(): int

Return Value:

int - Return download content size or file size in bytes.


getFileTime

Return the last-modified Unix timestamp of the source, or 0 whenunavailable.

public getFileTime(): int

Return Value:

int - Return download file ast-modified Unix timestamp.


getSource

Return the original download source as supplied to the constructor.

public getSource(): mixed

Return Value:

mixed - Return the download source.


getHeaders

Return the current download response headers.

public getHeaders(): array<string,mixed>

Return Value:

array<string,mixed> - Return an associate array of download prepared/sent headers.


getResponse

Build and return a PSR-7 Response without streaming to the client.

The returned Response wraps the source in a StreamInterface body.Call $downloader->close() after the body has been sent.

public getResponse(): Psr\Http\Message\ResponseInterface

Return Value:

\Psr\Http\Message\ResponseInterface - PSR-7 response ready to be dispatched.

Throws:

Use this when your framework's dispatch layer is responsible for sendingthe response (e.g. middleware pipelines, etc.).


download

Stream the download directly to the client.

Sends all necessary HTTP headers (including Content-Disposition,Content-Length, range headers for partial content, and cachingheaders), then streams the source content in chunks.

When $mode is X_SENDFILE or X_ACCEL, the method emits only theappropriate offload header and lets the web server handle the transfer;this is only effective when the source is a 'file'.

public download(int $mode = self::X_DOWNLOAD, int $chunkSize = 8192, int $delay = 0): bool

Parameters:

ParameterTypeDescription
$modeintTransfer strategy. One of:
- Downloader::X_DOWNLOAD – PHP streams the content (default).
- Downloader::X_SENDFILE – Apache/Lighttpd X-Sendfile.
- Downloader::X_ACCEL – Nginx X-Accel-Redirect.
$chunkSizeintBytes read per iteration when streaming (default 8192).
$delayintMicroseconds to sleep between chunks, 0 disables throttling (default 0).

Return Value:

bool - Returns true on success, false if the source yielded zero bytesor an offload header could not be set.

Throws:


tryDownload

Exception-safe wrapper around download().

Catches any Throwable thrown during streaming and returns falseinstead of propagating the exception. Useful in fire-and-forget contextswhere you prefer a boolean result over exception handling.

public tryDownload(int $mode = self::X_DOWNLOAD, int $chunkSize = 8192, int $delay = 0): bool

Parameters:

ParameterTypeDescription
$modeintTransfer strategy (X_DOWNLOAD, X_SENDFILE, X_ACCEL).
$chunkSizeintBytes per streaming chunk.
$delayintMicroseconds between chunks.

Return Value:

bool - Returns true on success, false on failure or exception.


close

Close all open handles and release internal state.

public close(): void

Call this after streaming is complete, or after obtaining and sending aPSR-7 response. The instance should not be reused after close().


deleteSourceFile

Delete the source file from disk.

public deleteSourceFile(): bool

Return Value:

bool - true on success, false when the source is not a file orthe unlink fails.

Note:

Only applicable when the source type is 'file'. Silently returns false for other source types.