File Uploads
ApnaPHP provides a simple and secure way to handle file uploads, including validation, storage, and retrieval.
1. Basic Upload
The fileUpload() helper function is used to handle file uploads. It takes the uploaded file instance (from Request::file()) and an optional subdirectory name.
<?php
use ApnaPHP\Routing\Request;
use ApnaPHP\Routing\Response;
use ApnaPHP\Support\Validation;
function POST(Request $request): Response
{
try {
// 1. Validate the incoming file
$validated = validate($request->all(), [
'avatar' => 'required|file|max_file_size:2048', // Max 2MB
]);
// 2. Get the uploaded file instance
$avatarFile = $request->file('avatar');
// 3. Create file upload helper
$uploadHelper = fileUpload($avatarFile, 'avatars');
// 4. Validate the file
$errors = $uploadHelper->validate([
'types' => ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
'max_size' => '2MB'
]);
if (!empty($errors)) {
return Response::json(['errors' => $errors], 422);
}
// 5. Upload the file
$path = $uploadHelper->upload();
return Response::json(['message' => 'Avatar uploaded successfully!', 'path' => $path]);
} catch (ValidationException $e) {
return Response::json(['errors' => $e->errors()], 422);
} catch (\Exception $e) {
return Response::json(['error' => $e->getMessage()], 500);
}
}
HTML Form Example
Ensure your HTML form has enctype="multipart/form-data":
<form action="/upload-avatar" method="POST" enctype="multipart/form-data">
<input type="file" name="avatar" accept="image/*">
<button type="submit">Upload Avatar</button>
</form>
2. File Upload Helper Methods
The FileUpload class provides various methods for handling uploaded files:
File Information
$uploadHelper = fileUpload($file, 'uploads');
// Check if file upload was successful
if ($uploadHelper->isValid()) {
// File is valid
}
// Check if file is an image
if ($uploadHelper->isImage()) {
// File is an image
}
// Get file information
$size = $uploadHelper->getSize(); // Size in bytes
$humanSize = $uploadHelper->getHumanSize(); // Size in human readable format (e.g., "2.5 MB")
$extension = $uploadHelper->getExtension(); // File extension (e.g., "jpg")
$basename = $uploadHelper->getBasename(); // File name without extension
$originalName = $uploadHelper->getOriginalName(); // Original file name
$mimeType = $uploadHelper->getMimeType(); // MIME type
$tempPath = $uploadHelper->getTemporaryPath(); // Temporary file path
File Validation
$errors = $uploadHelper->validate([
'types' => ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml'],
'max_size' => '5MB',
'min_size' => '1KB'
]);
if (!empty($errors)) {
// Handle validation errors
foreach ($errors as $error) {
echo $error;
}
}
File Upload
// Upload file with custom name
$path = $uploadHelper->upload('custom-filename.jpg');
// Upload file with auto-generated name
$path = $uploadHelper->upload(); // Generates unique filename
// Upload with specific directory structure
$path = $uploadHelper->upload('users/' . $userId . '/avatar.jpg');
3. File Validation Rules
You can validate uploaded files using the built-in validation rules:
$validated = validate($request->all(), [
'document' => 'required|file|max_file_size:5120', // Max 5MB
'profile_pic' => 'nullable|file|image|max_file_size:2048', // Max 2MB image
'avatar' => 'required|file|image|mimes:image/jpeg,image/png', // Only JPEG/PNG
]);
Available File Validation Rules
file: Ensures the input is a valid uploaded file.image: Ensures the file is an image (JPEG, PNG, GIF, BMP, SVG, WebP).mimes:type1,type2,...: Ensures the file has a MIME type corresponding to one of the listed types.max_file_size:value: Limits the file size in kilobytes.
4. Multiple Files Upload
You can upload multiple files by using an array input name in your HTML form (e.g., name="photos[]").
<form action="/upload-photos" method="POST" enctype="multipart/form-data">
<input type="file" name="photos[]" multiple accept="image/*">
<input type="file" name="documents[]" multiple accept=".pdf,.doc,.docx">
<button type="submit">Upload Files</button>
</form>
<?php
function POST(Request $request): Response
{
try {
$validated = validate($request->all(), [
'photos' => 'required|array|min:1|max:5', // At least 1, max 5 photos
'photos.*' => 'required|file|image|max_file_size:2048', // Each photo max 2MB
'documents' => 'nullable|array',
'documents.*' => 'file|max_file_size:5120', // Each doc max 5MB
]);
$uploadedPaths = [];
// Handle photos
foreach ($request->files('photos') as $photo) {
$uploadHelper = fileUpload($photo, 'gallery');
$uploadedPaths['photos'][] = $uploadHelper->upload();
}
// Handle documents
foreach ($request->files('documents') as $document) {
$uploadHelper = fileUpload($document, 'documents');
$uploadedPaths['documents'][] = $uploadHelper->upload();
}
return Response::json(['message' => 'Files uploaded successfully!', 'paths' => $uploadedPaths]);
} catch (ValidationException $e) {
return Response::json(['errors' => $e->errors()], 422);
} catch (\Exception $e) {
return Response::json(['error' => $e->getMessage()], 500);
}
}
5. File Operations
Serving Protected Files
To serve files stored in storage/uploads/, you need a route that authenticates the user and then uses Response::file() or Response::download().
<?php
use ApnaPHP\Routing\Request;
use ApnaPHP\Routing\Response;
function GET(Request $request, string $filename): Response
{
// Example: Protect documents, only authenticated users can download
if (!auth()->check()) {
return redirect('/login')->withError('Please login to download this file.');
}
$filePath = storage_path('uploads/documents/' . $filename);
if (!file_exists($filePath)) {
return notFound();
}
return Response::download($filePath, $filename); // Forces download
// Or Response::file($filePath); // Displays in browser if possible
}
Deleting Files
You can delete files using PHP's unlink() function after getting the full path.
$filePath = storage_path('uploads/avatars/some_unique_name.jpg');
if (file_exists($filePath)) {
unlink($filePath);
}
6. File Upload Configuration
By default, files are uploaded to storage/uploads/. You can specify a subdirectory to organize your uploads.
// Upload to storage/uploads/documents/
$uploadHelper = fileUpload($documentFile, 'documents');
// Upload to storage/uploads/temp/
$uploadHelper = fileUpload($tempFile, 'temp');
7. Security Features
ApnaPHP's file upload system incorporates several security best practices:
- MIME Type Validation:
mimesandimagerules check the actual file content, not just the extension. - Filename Sanitization: Uploaded filenames are sanitized to prevent directory traversal and other attacks.
- Unique Filenames: Files are stored with unique, generated names to prevent overwriting and improve security.
- Protected Storage: By default, files are stored outside the publicly accessible
public/directory (instorage/uploads/), requiring explicit routes to serve them. - Size Limits:
max_file_sizeprevents large file attacks.
8. Complete Example
Here's a complete file upload example with validation and error handling:
<?php
// app/upload/page.apna.php
use ApnaPHP\Routing\Request;
use ApnaPHP\Routing\Response;
function POST(Request $request): Response
{
try {
// Validate the request
$validated = validate($request->all(), [
'avatar' => 'required|file|image|max_file_size:2048|mimes:image/jpeg,image/png,image/gif,image/webp',
'name' => 'required|min:3|max:255',
]);
// Get the uploaded file
$avatarFile = $request->file('avatar');
$uploadHelper = fileUpload($avatarFile, 'avatars');
// Additional validation
$errors = $uploadHelper->validate([
'types' => ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
'max_size' => '2MB'
]);
if (!empty($errors)) {
return redirect('/upload')->withErrors(['avatar' => $errors])->withInput();
}
// Upload the file
$uploadedPath = $uploadHelper->upload();
// Save to database (example)
$user = auth()->user();
$user->avatar = $uploadedPath;
$user->save();
return redirect('/profile')->withSuccess('Avatar uploaded successfully!');
} catch (ValidationException $e) {
return redirect('/upload')->withErrors($e->errors())->withInput();
} catch (\Exception $e) {
return redirect('/upload')->withError('Upload failed: ' . $e->getMessage());
}
}
// Get validation errors and old input
$errors = getFlash('errors', []);
$oldInput = getFlash('old_input', []);
$success = getFlash('success');
?>
<!DOCTYPE html>
<html>
<head>
<title>Upload Avatar</title>
</head>
<body>
<?php if ($success): ?>
<div class="success"><?= $success ?></div>
<?php endif; ?>
<form method="POST" enctype="multipart/form-data">
<div>
<label>Avatar:</label>
<input type="file" name="avatar" accept="image/*">
<?php if (isset($errors['avatar'])): ?>
<span class="error"><?= is_array($errors['avatar']) ? implode(', ', $errors['avatar']) : $errors['avatar'] ?></span>
<?php endif; ?>
</div>
<div>
<label>Name:</label>
<input type="text" name="name" value="<?= old('name') ?>">
<?php if (isset($errors['name'])): ?>
<span class="error"><?= is_array($errors['name']) ? $errors['name'][0] : $errors['name'] ?></span>
<?php endif; ?>
</div>
<button type="submit">Upload Avatar</button>
</form>
</body>
</html>
9. Best Practices
- Always Validate: Never trust user-provided file data. Always validate MIME types, sizes, and extensions.
- Unique Names: Store files with unique, generated names to prevent conflicts and improve security.
- Protected Storage: Store uploaded files outside your web server's public document root.
- Access Control: Implement proper authentication and authorization checks before serving protected files.
- Cleanup Temporary Files: Ensure temporary upload files are properly handled and removed.
- Audit Logging: Log file upload activities for security auditing.
