File Manager
A powerful tool for uploading and managing files in PHP applications. Simplify storage, organize directories, and handle complex upload logic effortlessly within your codebase.
Basic File Upload
To upload files, create a form with enctype="multipart/form-data".
<form action="/" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<button>Upload</button>
</form>
Understanding $_FILES
The $_FILES array contains all information about the uploaded file.
$_FILES
Array
(
[file] => Array
(
[name] => test-upload.png
[full_path] => test-upload.png
[type] => image/png
[tmp_name] => C:\xampp\tmp\php1E10.tmp
[error] => 0
[size] => 107018
)
)
Handling Multiple Files
Add the multiple attribute to your input and append [] to the name.
<form action="/" method="post" enctype="multipart/form-data">
<input type="file" name="files[]" multiple />
<button>Upload</button>
</form>
<?php
if ($isPost) {
foreach ($_FILES['files']['name'] as $key => $name) {
if ($_FILES['files']['error'][$key] === 0) {
$tmpName = $_FILES['files']['tmp_name'][$key];
move_uploaded_file($tmpName, "uploads/$name");
}
}
}
?>
Common Error Codes
| Code | Meaning |
|---|---|
| 0 | Success. |
| 1 | Exceeds upload_max_filesize in php.ini. |
| 2 | Exceeds MAX_FILE_SIZE in HTML form. |
| 3 | File partially uploaded. |
| 4 | No file uploaded. |
| 6 | Missing temporary folder. |
| 7 | Failed to write to disk. |
Configuration
Setting Max File Size (HTML)
Use a hidden input before the file input.
<!-- 50 KB Limit -->
<input type="hidden" name="MAX_FILE_SIZE" value="51200" />
<input type="file" name="file" />
Moving Uploads (PHP)
Securely move files from temp storage.
<?php
move_uploaded_file(
$_FILES['file']['tmp_name'],
DOCUMENT_PATH . "/uploads/" . $_FILES['file']['name']
);
?>
PHP.ini Settings
| Directive | Default | Role |
|---|---|---|
| file_uploads | On | Enables or disables file uploads. |
| upload_max_filesize | 2M | Max size of a single file. Main |
| post_max_size | 8M | Max size of entire POST data. Main |
| max_file_uploads | 20 | Max number of files per request. |
The UploadFile Class
Located at src/Lib/FileManager/UploadFile.php, this class simplifies handling uploads.
Organization Tip
Store uploaded files outside src/app (logic only). Use /uploads in the root or src/uploads.
Key Features
- Built-in size validation
- Secure type restriction
- Filename cleanup & sanitization
- Handles multiple uploads automatically
Initialization
$upload = new UploadFile(DOCUMENT_PATH . '/uploads/');
Public Methods
-
upload(bool $renameDuplicates = true): voidUploads file(s) to the destination directory. Rename logic is optional.
-
update(array $file, string $oldFilename): boolReplaces an existing file with a new upload using the old filename.
-
setMaxSize(int $bytes): voidSets the maximum file size allowed for uploads (in bytes).
-
setPermittedTypes(array $mimeTypes): voidDefines allowed MIME types (e.g.
['image/png', 'application/pdf']). -
allowAllTypes(?string $suffix = null): voidDisables type checking. Optionally appends a suffix to files for safety.
-
rename(string $oldName, string $newName): boolRenames a file in the destination directory.
-
delete(string $filename): boolDeletes a file from the destination directory.
-
getMessages(): arrayReturns an array of status messages about the operation.
-
getErrorCode(): arrayReturns an array of error codes generated during upload.
-
getSuccessfulUploads(): arrayReturns an array with 'original' and 'final' names of uploaded files.
-
static convertToBytes(string $val): intHelper to convert shorthand sizes (e.g. '2M') to integer bytes.
-
static convertFromBytes(int $bytes): stringHelper to convert integer bytes to a readable string (e.g. '2.5 MB').
Full File Manager App
A complete example including upload, listing, renaming, and deleting functionality.
<?php
use Lib\Validator;
use Lib\StateManager;
use Lib\FileManager\UploadFile;
const UPLOADS = DOCUMENT_PATH . '/uploads/';
$maxSize = 50 * 1024; // 50KB
$uploadFile = new UploadFile(UPLOADS);
$uploadedFiles = scandir(UPLOADS);
$uploadMessage = StateManager::getState('uploadMessage', ['error' => [], 'message' => []]);
$serverMaxFiles = StateManager::getState('serverMaxFiles', ini_get('max_file_uploads'));
$serverPostMax = StateManager::getState('serverPostMax', UploadFile::convertToBytes(ini_get('post_max_size')));
$serverDisplayMax = StateManager::getState('serverDisplayMax', UploadFile::convertFromBytes($serverPostMax));
function uploadFile()
{
global $state, $uploadFile, $maxSize;
try {
$uploadFile->setMaxSize($maxSize);
$uploadFile->upload();
StateManager::setState('uploadMessage', ['error' => $uploadFile->getErrorCode(), 'message' => $uploadFile->getMessages()]);
} catch (Throwable $th) {
StateManager::setState('uploadMessage', ['error' => [(current($uploadFile->getErrorCode()) !== 0)], 'message' => [$th->getMessage()]]);
}
}
function deleteFile($data)
{
global $uploadFile;
try {
$uploadFile->delete($data->name);
StateManager::setState('uploadMessage', ['error' => [(current($uploadFile->getErrorCode()) !== 0)], 'message' => [current($uploadFile->getMessages())]]);
} catch (Throwable $th) {
return ['error' => true, 'message' => $th->getMessage()];
}
}
function renameFile($data)
{
global $uploadFile;
try {
if (($validatedValue = Validator::withRules($data->newName, 'required|min:3')) !== true) {
return ['error' => true, 'message' => $validatedValue];
}
$uploadFile->rename($data->oldFileName, $data->newName);
StateManager::setState('uploadMessage', ['error' => [(current($uploadFile->getErrorCode()) !== 0)], 'message' => [current($uploadFile->getMessages())]]);
} catch (Throwable $th) {
return ['error' => true, 'message' => $th->getMessage()];
}
}
?>
<div class="w-full max-w-2xl mx-auto p-6 flex flex-col gap-6">
<!-- Upload Form -->
<div class="bg-card border border-border rounded-xl p-6 shadow-sm">
<h1 class="text-2xl font-bold mb-4">Upload File</h1>
<ul class="list-disc ml-5 mb-4 text-sm text-muted-foreground">
<li>Simultaneous files: <?= $serverMaxFiles ?></li>
<li>Max file size: <?= UploadFile::convertFromBytes($maxSize) ?></li>
<li>Total limit: <?= $serverDisplayMax ?></li>
</ul>
<form class="flex flex-col gap-4" onsubmit="uploadFile" enctype="multipart/form-data" pp-suspense="{'disabled': true}">
<input type="hidden" name="MAX_FILE_SIZE" value="<?= $maxSize ?>" />
<input type="file" name="file[]" multiple class="block w-full text-sm border border-input rounded-lg cursor-pointer bg-background p-2" />
<button class="bg-primary text-primary-foreground py-2 rounded-lg hover:bg-primary/90 transition-all" pp-suspense="Uploading...">
Upload
</button>
</form>
</div>
<!-- Messages -->
<?php if ($uploadMessage['message']) : ?>
<div class="space-y-2" pp-display="10s">
<?php foreach ($uploadMessage['message'] as $key => $message) : ?>
<div class="p-3 rounded-lg text-sm <?= $uploadMessage['error'][$key] ? 'bg-destructive/10 text-destructive' : 'bg-green-500/10 text-green-500' ?>">
<?= $message ?>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<!-- File List -->
<div class="bg-card border border-border rounded-xl p-6 shadow-sm">
<h2 class="text-xl font-bold mb-4">Files</h2>
<div class="grid gap-2 max-h-[300px] overflow-y-auto">
<?php foreach ($uploadedFiles as $file) : ?>
<?php if ($file === '.' || $file === '..') continue; ?>
<div class="flex justify-between items-center p-3 bg-muted/30 rounded-lg border border-border/50">
<span class="text-sm font-mono truncate"><?= $file ?></span>
<div class="flex gap-2">
<button onclick="renameClickHandler({'name': '<?= $file ?>'})" class="text-muted-foreground hover:text-chart-3">Rename</button>
<button onclick="deleteClickHandler({'name': '<?= $file ?>'})" class="text-muted-foreground hover:text-destructive">Delete</button>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<!-- Dialogs (Delete & Rename) -->
<dialog id="deleteModal" class="p-6 bg-card border border-border rounded-xl shadow-2xl backdrop:bg-black/50">
<h3 class="text-lg font-bold mb-4">Confirm Deletion</h3>
<form onsubmit="deleteFile" class="flex gap-3">
<input type="hidden" id="deleteFileName" name="name" />
<button class="bg-destructive text-destructive-foreground px-4 py-2 rounded-lg">Delete</button>
<button type="button" onclick="this.closest('dialog').close()" class="bg-muted px-4 py-2 rounded-lg">Cancel</button>
</form>
</dialog>
<dialog id="renameModal" class="p-6 bg-card border border-border rounded-xl shadow-2xl backdrop:bg-black/50 w-full max-w-sm">
<h3 class="text-lg font-bold mb-4">Rename File</h3>
<form id="renameForm" onsubmit="renameFile" pp-after-request="renameFileValidation" class="flex flex-col gap-4">
<input type="hidden" id="oldFileName" name="oldFileName" />
<input id="newName" name="newName" class="border border-input bg-background rounded-lg p-2" placeholder="New filename" required />
<div class="flex gap-3 justify-end">
<button type="button" onclick="this.closest('dialog').close()" class="bg-muted px-4 py-2 rounded-lg">Cancel</button>
<button class="bg-primary text-primary-foreground px-4 py-2 rounded-lg">Rename</button>
</div>
<span id="renameMessage" class="text-destructive text-sm text-center"></span>
</form>
</dialog>
<script>
function deleteClickHandler(data) {
document.getElementById('deleteFileName').value = data.name;
document.getElementById('deleteModal').showModal();
}
function renameClickHandler(data) {
document.getElementById('oldFileName').value = data.name;
const fileName = data.name.split('.');
document.getElementById('newName').value = fileName[0];
document.getElementById('renameModal').showModal();
}
function renameFileValidation(data) {
if (data.response?.error) {
document.getElementById('renameMessage').innerText = data.response.message;
} else {
document.getElementById('renameMessage').innerText = '';
document.getElementById('renameModal').close();
}
}
</script>