Real-time file upload tracking in PHP with session upload progress

Tracking file upload progress is crucial for providing a good user experience, especially when handling large files. PHP offers a built-in solution through its Session Upload Progress functionality, which provides reliable and efficient progress tracking without additional dependencies.
Server configuration requirements
Before implementing upload progress tracking, ensure your server is properly configured:
; php.ini configuration
session.upload_progress.enabled = On
session.upload_progress.cleanup = On
session.upload_progress.prefix = "upload_progress_"
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
session.upload_progress.freq = "1%"
session.upload_progress.min_freq = "1"
upload_max_filesize = 10M
post_max_size = 10M
For Nginx, disable request buffering to ensure accurate progress tracking:
location /upload {
client_max_body_size 10M;
client_body_buffer_size 128k;
proxy_request_buffering off;
# ... other configuration
}
Implementing the upload handler
Create a secure upload handler that processes files and implements proper validation:
<?php
// upload.php
declare(strict_types=1);
session_start();
class FileUploadHandler {
private const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'application/pdf'];
private const MAX_FILE_SIZE = 10485760; // 10MB
private const UPLOAD_DIR = __DIR__ . '/uploads';
public function __construct() {
if (!is_dir(self::UPLOAD_DIR)) {
mkdir(self::UPLOAD_DIR, 0755, true);
}
}
public function handleUpload(): array {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
return ['error' => 'Invalid request method'];
}
if (!isset($_FILES['file'])) {
return ['error' => 'No file uploaded'];
}
$file = $_FILES['file'];
try {
$this->validateUpload($file);
$filename = $this->generateSafeFilename($file['name']);
$destination = self::UPLOAD_DIR . '/' . $filename;
if (!move_uploaded_file($file['tmp_name'], $destination)) {
throw new RuntimeException('Failed to move uploaded file');
}
return [
'success' => true,
'filename' => $filename,
'size' => $file['size']
];
} catch (Exception $e) {
return ['error' => $e->getMessage()];
}
}
private function validateUpload(array $file): void {
if ($file['error'] !== UPLOAD_ERR_OK) {
throw new RuntimeException($this->getUploadErrorMessage($file['error']));
}
if ($file['size'] > self::MAX_FILE_SIZE) {
throw new RuntimeException('File exceeds maximum size limit');
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($file['tmp_name']);
if (!in_array($mimeType, self::ALLOWED_TYPES, true)) {
throw new RuntimeException('Invalid file type');
}
}
private function generateSafeFilename(string $filename): string {
$extension = pathinfo($filename, PATHINFO_EXTENSION);
return bin2hex(random_bytes(16)) . '.' . $extension;
}
private function getUploadErrorMessage(int $error): string {
return match($error) {
UPLOAD_ERR_INI_SIZE => 'File exceeds upload_max_filesize',
UPLOAD_ERR_FORM_SIZE => 'File exceeds MAX_FILE_SIZE',
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 => 'A PHP extension stopped the upload',
default => 'Unknown upload error'
};
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$handler = new FileUploadHandler();
header('Content-Type: application/json');
echo json_encode($handler->handleUpload());
exit;
}
?>
Progress tracking implementation
Create a progress tracking endpoint that safely retrieves upload progress:
<?php
// progress.php
declare(strict_types=1);
session_start();
header('Content-Type: application/json');
$key = ini_get('session.upload_progress.prefix') . $_GET['id'] ?? '';
$progress = [];
if (isset($_SESSION[$key])) {
$current = $_SESSION[$key];
$progress = [
'lengthComputable' => true,
'loaded' => $current['bytes_processed'],
'total' => $current['content_length'],
'percentage' => ($current['bytes_processed'] / $current['content_length']) * 100
];
}
echo json_encode($progress);
Client-side integration
Implement a responsive upload form with progress tracking:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>File Upload with Progress</title>
<style>
.progress {
width: 100%;
height: 20px;
background: #f0f0f0;
border-radius: 4px;
overflow: hidden;
}
.progress-bar {
width: 0;
height: 100%;
background: #4caf50;
transition: width 0.3s ease;
}
</style>
</head>
<body>
<form id="uploadForm" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" id="progress-id" />
<input type="file" name="file" required />
<button type="submit">Upload</button>
<div class="progress">
<div class="progress-bar" id="progress-bar"></div>
</div>
<div id="status"></div>
</form>
<script>
const form = document.getElementById('uploadForm')
const progressBar = document.getElementById('progress-bar')
const status = document.getElementById('status')
const progressId = document.getElementById('progress-id')
form.addEventListener('submit', async (e) => {
e.preventDefault()
// Generate unique ID for this upload
const uploadId = Date.now().toString()
progressId.value = uploadId
const formData = new FormData(form)
try {
// Start progress tracking
const tracker = trackProgress(uploadId)
// Perform upload
const response = await fetch('upload.php', {
method: 'POST',
body: formData,
})
// Stop progress tracking
clearInterval(tracker)
const result = await response.json()
if (result.error) {
throw new Error(result.error)
}
status.textContent = 'Upload complete!'
progressBar.style.width = '100%'
} catch (error) {
status.textContent = `Error: ${error.message}`
progressBar.style.backgroundColor = '#f44336'
}
})
function trackProgress(uploadId) {
return setInterval(async () => {
try {
const response = await fetch(`progress.php?id=${uploadId}`)
const progress = await response.json()
if (progress.lengthComputable) {
const percentage = Math.round(progress.percentage)
progressBar.style.width = `${percentage}%`
status.textContent = `Uploading: ${percentage}%`
}
} catch (error) {
console.error('Progress tracking error:', error)
}
}, 1000)
}
</script>
</body>
</html>
Browser compatibility
This implementation works in all modern browsers that support:
- FormData API
- Fetch API
- CSS transitions
- ES6+ JavaScript features
For older browsers, consider adding polyfills or using a more traditional XMLHttpRequest approach.
Summary
PHP's Session Upload Progress provides a reliable way to track file uploads without external dependencies. By combining it with proper security measures and error handling, you can create a robust upload system that provides real-time feedback to users.
For more advanced file upload capabilities, including chunked uploads and resume functionality, consider exploring the tus protocol implementation for PHP.
Happy coding!