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')
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.
}
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)
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
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>
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