Pular para o conteúdo principal

File Uploads

RestServer provides a convenient UploadedFiles class to handle file uploads with a clean, object-oriented API. This class simplifies working with PHP's $_FILES superglobal and provides useful methods for validating, accessing, and saving uploaded files.

Accessing Uploaded Files​

Access uploaded files through the HttpRequest::uploadedFiles() method:

<?php
use ByJG\RestServer\HttpResponse;
use ByJG\RestServer\HttpRequest;
use ByJG\RestServer\Route\Route;

Route::post('/upload')
->withClosure(function (HttpResponse $response, HttpRequest $request) {
$uploadedFiles = $request->uploadedFiles();

// Work with uploaded files
if ($uploadedFiles->count() > 0) {
$response->write(['message' => 'Files uploaded successfully']);
} else {
$response->write(['message' => 'No files uploaded']);
}
});

UploadedFiles Methods​

Checking File Count​

Get the number of uploaded files:

<?php
$count = $uploadedFiles->count();
// Returns: int - Number of files in $_FILES

Getting File Keys​

Get all upload field names:

<?php
$keys = $uploadedFiles->getKeys();
// Returns: array - ['file1', 'file2', 'avatar', ...]

// Example: Check if specific file was uploaded
if (in_array('avatar', $keys)) {
// Process avatar
}

Validating Upload Success​

Check if a file was uploaded successfully (no errors):

<?php
if ($uploadedFiles->isOk('avatar')) {
// File uploaded successfully
$uploadedFiles->saveTo('avatar', '/path/to/uploads');
} else {
// Upload failed
$errorCode = $uploadedFiles->getErrorCode('avatar');
// Handle error
}

Getting Error Code​

Retrieve PHP upload error code for a file:

<?php
$errorCode = $uploadedFiles->getErrorCode('avatar');

// Error codes (PHP constants):
// UPLOAD_ERR_OK (0) - No error, file uploaded successfully
// UPLOAD_ERR_INI_SIZE (1) - File exceeds upload_max_filesize
// UPLOAD_ERR_FORM_SIZE (2) - File exceeds MAX_FILE_SIZE from form
// UPLOAD_ERR_PARTIAL (3) - File was only partially uploaded
// UPLOAD_ERR_NO_FILE (4) - No file was uploaded
// UPLOAD_ERR_NO_TMP_DIR (6) - Missing temporary folder
// UPLOAD_ERR_CANT_WRITE (7) - Failed to write file to disk
// UPLOAD_ERR_EXTENSION (8) - Upload stopped by PHP extension

Getting File Information​

File Name​

<?php
$fileName = $uploadedFiles->getFileName('avatar');
// Returns: string - Original filename from client (e.g., 'profile.jpg')

File Type​

<?php
$fileType = $uploadedFiles->getFileType('avatar');
// Returns: string - MIME type (e.g., 'image/jpeg', 'application/pdf')
cuidado

The MIME type comes from the client and can be spoofed. Always validate file types server-side by checking file contents, not just the reported MIME type.

File Size​

<?php
$fileSize = $uploadedFiles->getFileSize('avatar');
// Returns: int - File size in bytes

Reading File Contents​

Read the uploaded file contents into memory:

<?php
$contents = $uploadedFiles->getUploadedFile('avatar');
// Returns: string|false - File contents or false on failure

if ($contents !== false) {
// Process file contents
// Example: Parse CSV, analyze image, etc.
}
aviso

Be cautious when reading large files into memory. Use saveTo() for large files instead.

Saving Files​

Save an uploaded file to a destination directory:

<?php
// Save with original filename
$uploadedFiles->saveTo('avatar', '/path/to/uploads');
// Result: /path/to/uploads/original-filename.jpg

// Save with custom filename
$uploadedFiles->saveTo('avatar', '/path/to/uploads', 'user-123-avatar.jpg');
// Result: /path/to/uploads/user-123-avatar.jpg

Parameters:

  • $key - Upload field name
  • $destinationPath - Destination directory path (without trailing slash)
  • $newName - Optional new filename (defaults to original filename)
dica

The destination directory must exist and be writable by the web server user.

Cleaning Up Temporary Files​

Remove uploaded temporary file manually (not usually needed):

<?php
$uploadedFiles->clearTemp('avatar');
// Deletes the temporary file from server
informação

PHP automatically deletes temporary files at the end of request execution, so you typically don't need to call this method.

Complete Upload Example​

Here's a complete example handling file uploads with validation:

<?php
use ByJG\RestServer\HttpResponse;
use ByJG\RestServer\HttpRequest;
use ByJG\RestServer\Route\Route;

Route::post('/upload/avatar')
->withClosure(function (HttpResponse $response, HttpRequest $request) {
$uploadedFiles = $request->uploadedFiles();

// Check if file was uploaded
if ($uploadedFiles->count() === 0) {
$response->setResponseCode(400);
$response->write(['error' => 'No file uploaded']);
return;
}

$fileKey = 'avatar';

// Check if specific file exists
if (!in_array($fileKey, $uploadedFiles->getKeys())) {
$response->setResponseCode(400);
$response->write(['error' => 'Avatar field not found']);
return;
}

// Check for upload errors
if (!$uploadedFiles->isOk($fileKey)) {
$errorCode = $uploadedFiles->getErrorCode($fileKey);
$response->setResponseCode(400);
$response->write(['error' => 'Upload failed', 'code' => $errorCode]);
return;
}

// Validate file type
$fileType = $uploadedFiles->getFileType($fileKey);
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];

if (!in_array($fileType, $allowedTypes)) {
$response->setResponseCode(400);
$response->write(['error' => 'Invalid file type. Only images allowed.']);
return;
}

// Validate file size (2MB max)
$fileSize = $uploadedFiles->getFileSize($fileKey);
$maxSize = 2 * 1024 * 1024; // 2MB in bytes

if ($fileSize > $maxSize) {
$response->setResponseCode(400);
$response->write(['error' => 'File too large. Maximum 2MB allowed.']);
return;
}

// Generate unique filename
$originalName = $uploadedFiles->getFileName($fileKey);
$extension = pathinfo($originalName, PATHINFO_EXTENSION);
$newFilename = uniqid('avatar_', true) . '.' . $extension;

// Save file
$uploadDir = __DIR__ . '/../uploads/avatars';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}

try {
$uploadedFiles->saveTo($fileKey, $uploadDir, $newFilename);

$response->write([
'success' => true,
'filename' => $newFilename,
'size' => $fileSize,
'type' => $fileType,
]);
} catch (\Exception $e) {
$response->setResponseCode(500);
$response->write(['error' => 'Failed to save file']);
}
});

Multiple File Upload Example​

Handling multiple files from a single form:

<?php
use ByJG\RestServer\HttpResponse;
use ByJG\RestServer\HttpRequest;
use ByJG\RestServer\Route\Route;

Route::post('/upload/documents')
->withClosure(function (HttpResponse $response, HttpRequest $request) {
$uploadedFiles = $request->uploadedFiles();

if ($uploadedFiles->count() === 0) {
$response->setResponseCode(400);
$response->write(['error' => 'No files uploaded']);
return;
}

$uploadDir = __DIR__ . '/../uploads/documents';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}

$results = [];
$keys = $uploadedFiles->getKeys();

foreach ($keys as $key) {
if (!$uploadedFiles->isOk($key)) {
$results[$key] = [
'success' => false,
'error' => 'Upload error: ' . $uploadedFiles->getErrorCode($key),
];
continue;
}

$filename = $uploadedFiles->getFileName($key);
$newFilename = time() . '_' . $filename;

try {
$uploadedFiles->saveTo($key, $uploadDir, $newFilename);
$results[$key] = [
'success' => true,
'filename' => $newFilename,
'size' => $uploadedFiles->getFileSize($key),
];
} catch (\Exception $e) {
$results[$key] = [
'success' => false,
'error' => 'Failed to save file',
];
}
}

$response->write(['results' => $results]);
});

HTML Form Example​

Example HTML form for uploading files:

<!DOCTYPE html>
<html>
<head>
<title>File Upload</title>
</head>
<body>
<h1>Upload Avatar</h1>

<!-- Single file upload -->
<form action="/upload/avatar" method="POST" enctype="multipart/form-data">
<label>
Avatar:
<input type="file" name="avatar" accept="image/*" required>
</label>
<button type="submit">Upload</button>
</form>

<hr>

<h1>Upload Multiple Documents</h1>

<!-- Multiple file upload -->
<form action="/upload/documents" method="POST" enctype="multipart/form-data">
<label>
Document 1:
<input type="file" name="doc1" required>
</label>
<br>
<label>
Document 2:
<input type="file" name="doc2" required>
</label>
<br>
<label>
Document 3:
<input type="file" name="doc3">
</label>
<br>
<button type="submit">Upload Documents</button>
</form>
</body>
</html>
important

Always set enctype="multipart/form-data" on forms that upload files.

Security Best Practices​

1. Validate File Types​

Never trust the client-reported MIME type. Validate by checking file contents:

<?php
// Check file signature (magic bytes)
$fileContents = $uploadedFiles->getUploadedFile('avatar');
$finfo = new finfo(FILEINFO_MIME_TYPE);
$realMimeType = $finfo->buffer($fileContents);

if (!in_array($realMimeType, ['image/jpeg', 'image/png'])) {
// Reject file
}

2. Limit File Size​

Configure PHP and validate file size:

// php.ini
// upload_max_filesize = 2M
// post_max_size = 2M

// In your code
$maxSize = 2 * 1024 * 1024; // 2MB
if ($uploadedFiles->getFileSize('avatar') > $maxSize) {
// Reject file
}

3. Generate Unique Filenames​

Never use client-provided filenames directly:

<?php
// BAD: Security risk
$filename = $uploadedFiles->getFileName('avatar');
$uploadedFiles->saveTo('avatar', $uploadDir, $filename);

// GOOD: Generate safe filename
$extension = pathinfo($uploadedFiles->getFileName('avatar'), PATHINFO_EXTENSION);
$safeFilename = uniqid('upload_', true) . '.' . $extension;
$uploadedFiles->saveTo('avatar', $uploadDir, $safeFilename);

4. Store Outside Web Root​

Store uploaded files outside the web-accessible directory:

<?php
// BAD: Files accessible via web
$uploadDir = __DIR__ . '/../public/uploads';

// GOOD: Files not directly accessible
$uploadDir = __DIR__ . '/../storage/uploads';
// Serve files through a controller with access control

5. Scan for Malware​

For production systems, consider scanning uploaded files:

<?php
// Example using ClamAV
$uploadedFiles->saveTo('file', $tempDir, $tempFilename);
$result = shell_exec("clamscan " . escapeshellarg($tempDir . '/' . $tempFilename));

if (strpos($result, 'FOUND') !== false) {
// Malware detected
unlink($tempDir . '/' . $tempFilename);
// Reject upload
}

Error Handling​

Handle common upload errors:

<?php
use ByJG\RestServer\HttpResponse;
use ByJG\RestServer\HttpRequest;

function handleUploadError(int $errorCode): string
{
return match($errorCode) {
UPLOAD_ERR_OK => 'No error',
UPLOAD_ERR_INI_SIZE => 'File exceeds upload_max_filesize directive',
UPLOAD_ERR_FORM_SIZE => 'File exceeds MAX_FILE_SIZE from HTML form',
UPLOAD_ERR_PARTIAL => 'File was only partially uploaded',
UPLOAD_ERR_NO_FILE => 'No file was uploaded',
UPLOAD_ERR_NO_TMP_DIR => 'Missing temporary folder',
UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk',
UPLOAD_ERR_EXTENSION => 'Upload stopped by PHP extension',
default => 'Unknown error',
};
}

Route::post('/upload')
->withClosure(function (HttpResponse $response, HttpRequest $request) {
$uploadedFiles = $request->uploadedFiles();

if (!$uploadedFiles->isOk('file')) {
$errorCode = $uploadedFiles->getErrorCode('file');
$errorMessage = handleUploadError($errorCode);

$response->setResponseCode(400);
$response->write([
'error' => $errorMessage,
'code' => $errorCode,
]);
return;
}

// Process file...
});

API Reference​

UploadedFiles​

count(): int​

Returns the number of uploaded files.

getKeys(): array​

Returns an array of all upload field names.

isOk(string $key): bool​

Checks if a file was uploaded successfully without errors.

Parameters:

  • $key - Upload field name

Returns: true if upload was successful, false otherwise

getErrorCode(string $key): int|string|null​

Returns the PHP upload error code for a file.

Parameters:

  • $key - Upload field name

Returns: Error code (0 = success, see UPLOAD_ERR_* constants)

getUploadedFile(string $key): string|false​

Reads and returns the file contents.

Parameters:

  • $key - Upload field name

Returns: File contents as string, or false on failure

getFileName(string $key): int|string|null​

Returns the original filename from the client.

Parameters:

  • $key - Upload field name

Returns: Original filename

getFileType(string $key): int|string|null​

Returns the MIME type reported by the client.

Parameters:

  • $key - Upload field name

Returns: MIME type (e.g., 'image/jpeg')

saveTo(string $key, string $destinationPath, string $newName = ""): void​

Saves the uploaded file to a destination directory.

Parameters:

  • $key - Upload field name
  • $destinationPath - Destination directory path
  • $newName - Optional new filename (defaults to original)

Throws: InvalidArgumentException if upload key doesn't exist

clearTemp(string $key): void​

Manually deletes the temporary uploaded file.

Parameters:

  • $key - Upload field name

getFileSize(string $key): int|string|null​

Returns the file size in bytes.

Parameters:

  • $key - Upload field name

Returns: File size in bytes

See Also​