ApnaPHP

Documentation

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: mimes and image rules 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 (in storage/uploads/), requiring explicit routes to serve them.
  • Size Limits: max_file_size prevents 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.