79 lines
2.5 KiB
PHP
79 lines
2.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types = 1);
|
|
|
|
namespace KTXC\Http\Response;
|
|
|
|
/**
|
|
* Simple file response that reads a file from disk and serves it.
|
|
*
|
|
* Only supports sending full file contents (no range / streaming for now).
|
|
*/
|
|
class FileResponse extends Response
|
|
{
|
|
private string $filePath;
|
|
|
|
public function __construct(string $filePath, int $status = 200, array $headers = [])
|
|
{
|
|
if (!is_file($filePath) || !is_readable($filePath)) {
|
|
throw new \InvalidArgumentException(sprintf('FileResponse: file not found or not readable: %s', $filePath));
|
|
}
|
|
|
|
$this->filePath = $filePath;
|
|
|
|
// Determine content type (very small helper; rely on common extensions)
|
|
$mime = self::guessMimeType($filePath) ?? 'application/octet-stream';
|
|
$headers['Content-Type'] = $headers['Content-Type'] ?? $mime;
|
|
$headers['Content-Length'] = (string) filesize($filePath);
|
|
$headers['Last-Modified'] = gmdate('D, d M Y H:i:s', filemtime($filePath)) . ' GMT';
|
|
$headers['Cache-Control'] = $headers['Cache-Control'] ?? 'public, max-age=60';
|
|
|
|
parent::__construct('', $status, $headers);
|
|
|
|
// Defer reading file until sendContent to avoid memory usage.
|
|
}
|
|
|
|
public function getFilePath(): string
|
|
{
|
|
return $this->filePath;
|
|
}
|
|
|
|
public function sendContent(): static
|
|
{
|
|
// Output file contents directly
|
|
readfile($this->filePath);
|
|
return $this;
|
|
}
|
|
|
|
private static function guessMimeType(string $filePath): ?string
|
|
{
|
|
$ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
|
|
return match ($ext) {
|
|
'html', 'htm' => 'text/html; charset=UTF-8',
|
|
'css' => 'text/css; charset=UTF-8',
|
|
'js' => 'application/javascript; charset=UTF-8',
|
|
'json' => 'application/json; charset=UTF-8',
|
|
'png' => 'image/png',
|
|
'jpg', 'jpeg' => 'image/jpeg',
|
|
'gif' => 'image/gif',
|
|
'svg' => 'image/svg+xml',
|
|
'txt' => 'text/plain; charset=UTF-8',
|
|
'xml' => 'application/xml; charset=UTF-8',
|
|
default => self::finfoMime($filePath),
|
|
};
|
|
}
|
|
|
|
private static function finfoMime(string $filePath): ?string
|
|
{
|
|
if (function_exists('finfo_open')) {
|
|
$f = finfo_open(FILEINFO_MIME_TYPE);
|
|
if ($f) {
|
|
$mime = finfo_file($f, $filePath) ?: null;
|
|
finfo_close($f);
|
|
return $mime;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|