Skip to content

Commit

Permalink
Merge pull request #601 from pierresh/master
Browse files Browse the repository at this point in the history
feat: method to download files easily
  • Loading branch information
n0nag0n authored Jul 27, 2024
2 parents 7cfaca8 + f697e30 commit c834f4c
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 3 deletions.
44 changes: 42 additions & 2 deletions flight/Engine.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@
* @method void jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0)
* Sends a JSONP response.
*
* # HTTP caching
* # HTTP methods
* @method void etag(string $id, ('strong'|'weak') $type = 'strong') Handles ETag HTTP caching.
* @method void lastModified(int $time) Handles last modified HTTP caching.
* @method void download(string $filePath) Downloads a file
*
* phpcs:disable PSR2.Methods.MethodDeclaration.Underscore
*/
Expand All @@ -76,7 +77,7 @@ class Engine
private const MAPPABLE_METHODS = [
'start', 'stop', 'route', 'halt', 'error', 'notFound',
'render', 'redirect', 'etag', 'lastModified', 'json', 'jsonHalt', 'jsonp',
'post', 'put', 'patch', 'delete', 'group', 'getUrl'
'post', 'put', 'patch', 'delete', 'group', 'getUrl', 'download'
];

/** @var array<string, mixed> Stored variables. */
Expand Down Expand Up @@ -895,6 +896,45 @@ public function _jsonp(
}
}

/**
* Downloads a file
*
* @param string $filePath The path to the file to download
* @throws Exception If the file cannot be found
*
* @return void
*/
public function _download(string $filePath): void {
if (file_exists($filePath) === false) {
throw new Exception("$filePath cannot be found.");
}

$fileSize = filesize($filePath);

$mimeType = mime_content_type($filePath);
$mimeType = $mimeType !== false ? $mimeType : 'application/octet-stream';

$response = $this->response();
$response->send();
$response->setRealHeader('Content-Description: File Transfer');
$response->setRealHeader('Content-Type: ' . $mimeType);
$response->setRealHeader('Content-Disposition: attachment; filename="' . basename($filePath) . '"');
$response->setRealHeader('Expires: 0');
$response->setRealHeader('Cache-Control: must-revalidate');
$response->setRealHeader('Pragma: public');
$response->setRealHeader('Content-Length: ' . $fileSize);

// // Clear the output buffer
ob_clean();
flush();

// // Read the file and send it to the output buffer
readfile($filePath);
if(empty(getenv('PHPUNIT_TEST'))) {
exit; // @codeCoverageIgnore
}
}

/**
* Handles ETag HTTP caching.
*
Expand Down
3 changes: 2 additions & 1 deletion flight/Flight.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,10 @@
* @method static void error(Throwable $exception) Sends an HTTP 500 response.
* @method static void notFound() Sends an HTTP 404 response.
*
* # HTTP caching
* # HTTP methods
* @method static void etag(string $id, ('strong'|'weak') $type = 'strong') Performs ETag HTTP caching.
* @method static void lastModified(int $time) Performs last modified HTTP caching.
* @method static void download(string $filePath) Downloads a file
*/
class Flight
{
Expand Down
34 changes: 34 additions & 0 deletions tests/EngineTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -952,4 +952,38 @@ public function setRealHeader(
$this->assertEquals('Method Not Allowed', $engine->response()->getBody());
}

public function testDownload()
{
$engine = new class extends Engine {
public function getLoader()
{
return $this->loader;
}
};
// doing this so we can overwrite some parts of the response
$engine->getLoader()->register('response', function () {
return new class extends Response {
public function setRealHeader(
string $header_string,
bool $replace = true,
int $response_code = 0
): self {
return $this;
}
};
});
$tmpfile = tmpfile();
fwrite($tmpfile, 'I am a teapot');
$streamPath = stream_get_meta_data($tmpfile)['uri'];
$this->expectOutputString('I am a teapot');
$engine->download($streamPath);
}

public function testDownloadBadPath() {
$engine = new Engine();
$this->expectException(Exception::class);
$this->expectExceptionMessage("/path/to/nowhere cannot be found.");
$engine->download('/path/to/nowhere');
}

}
1 change: 1 addition & 0 deletions tests/server/LayoutMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public function before()
<li><a href="/dice">Dice Container</a></li>
<li><a href="/no-container">No Container Registered</a></li>
<li><a href="/Pascal_Snake_Case">Pascal_Snake_Case</a></li>
<li><a href="/download">Download File</a></li>
</ul>
HTML;
echo '<div id="container">';
Expand Down
5 changes: 5 additions & 0 deletions tests/server/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@
Flight::jsonHalt(['message' => 'JSON rendered and halted successfully with no other body content!']);
});

// Download a file
Flight::route('/download', function () {
Flight::download('test_file.txt');
});

Flight::map('error', function (Throwable $e) {
echo sprintf(
<<<HTML
Expand Down
1 change: 1 addition & 0 deletions tests/server/test_file.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This file downloaded successfully!

0 comments on commit c834f4c

Please sign in to comment.