Skip to content

Commit

Permalink
Merge pull request #602 from flightphp/file-upload-handler
Browse files Browse the repository at this point in the history
Added ability to handle file uploads in a simple way
  • Loading branch information
n0nag0n authored Aug 22, 2024
2 parents c834f4c + b6220f7 commit 63fbf9b
Show file tree
Hide file tree
Showing 7 changed files with 406 additions and 71 deletions.
49 changes: 12 additions & 37 deletions flight/Engine.php
Original file line number Diff line number Diff line change
Expand Up @@ -896,43 +896,18 @@ 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
}
/**
* 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
{
$this->response()->downloadFile($filePath);
}

/**
Expand Down
59 changes: 59 additions & 0 deletions flight/net/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -414,4 +414,63 @@ public static function getScheme(): string

return 'http';
}

/**
* Retrieves the array of uploaded files.
*
* @return array<string, array<string,UploadedFile>|array<string,array<string,UploadedFile>>> The array of uploaded files.
*/
public function getUploadedFiles(): array
{
$files = [];
$correctedFilesArray = $this->reArrayFiles($this->files);
foreach ($correctedFilesArray as $keyName => $files) {
foreach ($files as $file) {
$UploadedFile = new UploadedFile(
$file['name'],
$file['type'],
$file['size'],
$file['tmp_name'],
$file['error']
);
if (count($files) > 1) {
$files[$keyName][] = $UploadedFile;
} else {
$files[$keyName] = $UploadedFile;
}
}
}

return $files;
}

/**
* Re-arranges the files in the given files collection.
*
* @param Collection $filesCollection The collection of files to be re-arranged.
*
* @return array<string, array<int, array<string, mixed>>> The re-arranged files collection.
*/
protected function reArrayFiles(Collection $filesCollection): array
{

$fileArray = [];
foreach ($filesCollection as $fileKeyName => $file) {
$isMulti = is_array($file['name']) === true && count($file['name']) > 1;
$fileCount = $isMulti === true ? count($file['name']) : 1;
$fileKeys = array_keys($file);

for ($i = 0; $i < $fileCount; $i++) {
foreach ($fileKeys as $key) {
if ($isMulti === true) {
$fileArray[$fileKeyName][$i][$key] = $file[$key][$i];
} else {
$fileArray[$fileKeyName][$i][$key] = $file[$key];
}
}
}
}

return $fileArray;
}
}
38 changes: 38 additions & 0 deletions flight/net/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -480,4 +480,42 @@ protected function processResponseCallbacks(): void
$this->body = $callback($this->body);
}
}

/**
* Downloads a file.
*
* @param string $filePath The path to the file to be downloaded.
*
* @return void
*/
public function downloadFile(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';

$this->send();
$this->setRealHeader('Content-Description: File Transfer');
$this->setRealHeader('Content-Type: ' . $mimeType);
$this->setRealHeader('Content-Disposition: attachment; filename="' . basename($filePath) . '"');
$this->setRealHeader('Expires: 0');
$this->setRealHeader('Cache-Control: must-revalidate');
$this->setRealHeader('Pragma: public');
$this->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
}
}
}
157 changes: 157 additions & 0 deletions flight/net/UploadedFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<?php

declare(strict_types=1);

namespace flight\net;

use Exception;

class UploadedFile
{
/**
* @var string $name The name of the uploaded file.
*/
private string $name;

/**
* @var string $mimeType The MIME type of the uploaded file.
*/
private string $mimeType;

/**
* @var int $size The size of the uploaded file in bytes.
*/
private int $size;

/**
* @var string $tmpName The temporary name of the uploaded file.
*/
private string $tmpName;

/**
* @var int $error The error code associated with the uploaded file.
*/
private int $error;

/**
* Constructs a new UploadedFile object.
*
* @param string $name The name of the uploaded file.
* @param string $mimeType The MIME type of the uploaded file.
* @param int $size The size of the uploaded file in bytes.
* @param string $tmpName The temporary name of the uploaded file.
* @param int $error The error code associated with the uploaded file.
*/
public function __construct(string $name, string $mimeType, int $size, string $tmpName, int $error)
{
$this->name = $name;
$this->mimeType = $mimeType;
$this->size = $size;
$this->tmpName = $tmpName;
$this->error = $error;
}

/**
* Retrieves the client-side filename of the uploaded file.
*
* @return string The client-side filename.
*/
public function getClientFilename(): string
{
return $this->name;
}

/**
* Retrieves the media type of the uploaded file as provided by the client.
*
* @return string The media type of the uploaded file.
*/
public function getClientMediaType(): string
{
return $this->mimeType;
}

/**
* Returns the size of the uploaded file.
*
* @return int The size of the uploaded file.
*/
public function getSize(): int
{
return $this->size;
}

/**
* Retrieves the temporary name of the uploaded file.
*
* @return string The temporary name of the uploaded file.
*/
public function getTempName(): string
{
return $this->tmpName;
}

/**
* Get the error code associated with the uploaded file.
*
* @return int The error code.
*/
public function getError(): int
{
return $this->error;
}

/**
* Moves the uploaded file to the specified target path.
*
* @param string $targetPath The path to move the file to.
*
* @return void
*/
public function moveTo(string $targetPath): void
{
if ($this->error !== UPLOAD_ERR_OK) {
throw new Exception($this->getUploadErrorMessage($this->error));
}

$isUploadedFile = is_uploaded_file($this->tmpName) === true;
if (
$isUploadedFile === true
&&
move_uploaded_file($this->tmpName, $targetPath) === false
) {
throw new Exception('Cannot move uploaded file'); // @codeCoverageIgnore
} elseif ($isUploadedFile === false && getenv('PHPUNIT_TEST')) {
rename($this->tmpName, $targetPath);
}
}

/**
* Retrieves the error message for a given upload error code.
*
* @param int $error The upload error code.
*
* @return string The error message.
*/
protected function getUploadErrorMessage(int $error): string
{
switch ($error) {
case UPLOAD_ERR_INI_SIZE:
return 'The uploaded file exceeds the upload_max_filesize directive in php.ini.';
case UPLOAD_ERR_FORM_SIZE:
return 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.';
case UPLOAD_ERR_PARTIAL:
return 'The uploaded file was only partially uploaded.';
case UPLOAD_ERR_NO_FILE:
return 'No file was uploaded.';
case UPLOAD_ERR_NO_TMP_DIR:
return 'Missing a temporary folder.';
case UPLOAD_ERR_CANT_WRITE:
return 'Failed to write file to disk.';
case UPLOAD_ERR_EXTENSION:
return 'A PHP extension stopped the file upload.';
default:
return 'An unknown error occurred. Error code: ' . $error;
}
}
}
Loading

0 comments on commit 63fbf9b

Please sign in to comment.