-
Notifications
You must be signed in to change notification settings - Fork 2
Upload
A lightweight, fluent PHP class for handling single and multiple file uploads securely.
- PHP 8.1 or higher
-
fileinfoextension (enabled by default in most PHP builds)
Copy Upload.php into your project and include or autoload it via your framework or Composer.
use Webrium\Upload;<form method="POST" enctype="multipart/form-data">
<input type="file" name="avatar">
<button type="submit">Upload</button>
</form>$upload = Upload::fromInput('avatar');
if ($upload === null) {
echo 'No file was submitted.';
exit;
}
$result = $upload
->maxMB(2)
->allowExtension('jpg, png, webp')
->allowMimeType('image/jpeg, image/png, image/webp')
->to('/var/www/uploads/avatars')
->useRandomName()
->save();
if ($result === false) {
echo $upload->getFirstError();
} else {
echo "Saved as: $result";
}<input type="file" name="photos[]" multiple>$uploads = Upload::fromInput('photos');
if ($uploads === null) {
exit('No files uploaded.');
}
// fromInput() returns an array when the input name ends with []
foreach ($uploads as $upload) {
$result = $upload
->maxMB(5)
->allowExtension(['jpg', 'png'])
->to('/var/www/uploads/photos')
->useRandomName()
->save();
if ($result === false) {
echo "Failed: " . $upload->getFirstError() . "\n";
} else {
echo "Saved: $result\n";
}
}Creates one or more Upload instances from $_FILES.
| Return value | Meaning |
|---|---|
Upload |
A single file input was submitted |
Upload[] |
A multiple file input (name[]) was submitted |
null |
The input was missing or empty |
All configuration methods return $this, so they can be chained.
Set the maximum allowed file size in kilobytes.
$upload->maxKB(512); // 512 KBSet the maximum allowed file size in megabytes.
$upload->maxMB(10); // 10 MBRestrict uploads to specific file extensions. Accepts a comma-separated string or an array. Leading dots and letter case are normalized automatically.
$upload->allowExtension('jpg, png, gif');
$upload->allowExtension(['.JPG', 'PNG', 'gif']);Restrict uploads by real MIME type, detected by reading the file's binary content — not the browser-reported value. This prevents MIME-spoofing attacks.
$upload->allowMimeType('image/jpeg, image/png');
$upload->allowMimeType(['application/pdf']);Tip: Use both
allowExtension()andallowMimeType()together for the strongest validation.
Set the destination directory. The directory is created automatically (recursively) if it does not exist.
$upload->to('/var/www/storage/uploads');Save the file with a custom base name. The original file's extension is always preserved regardless of what extension is passed in $name, preventing extension spoofing.
$upload->asName('profile-picture');
// Result: profile-picture.jpg (extension taken from the uploaded file)Generate a cryptographically random hex filename.
$upload->useRandomName();
// Result: a3f8c21d...b92e.pngBy default, if a file with the same name already exists, a numeric suffix is appended (file-1.jpg, file-2.jpg, …). Call this method to allow overwriting instead.
$upload->allowOverwrite(); // allow overwriting
$upload->allowOverwrite(false); // back to default (prevent overwrite)Run all configured validation rules without saving the file. Returns true if the file passes all checks.
if (!$upload->validate()) {
print_r($upload->getErrors());
}Validate and move the uploaded file to the destination directory.
- Returns the final filename (string) on success.
- Returns
falseon failure and populates the error list. - If
$throwOnErroristrue, throws anExceptionon the first failure instead.
// Default — check return value
$filename = $upload->save();
// Exception mode
try {
$filename = $upload->save(throwOnError: true);
} catch (Exception $e) {
echo $e->getMessage();
}| Method | Return type | Description |
|---|---|---|
getOriginalName() |
string |
The original filename as submitted by the browser |
getExtension() |
string |
Lowercase extension extracted from the original name |
getSize() |
int |
File size in bytes |
getMimeType() |
string |
Real MIME type detected from file contents |
getErrors() |
array |
All validation/save error messages |
getFirstError() |
string |
The first error message, or an empty string |
| Feature | Details |
|---|---|
| Path traversal prevention |
sanitizeFileName() applies basename() and strips all characters except alphanumerics, dots, hyphens, and underscores |
| Double extension blocking |
image.php.jpg becomes image-php.jpg
|
| MIME spoofing prevention | MIME type is read from the file's binary content using finfo, not the browser header |
| Custom name extension lock |
asName() always uses the real extension from the uploaded file — callers cannot inject a different one |
is_uploaded_file() check |
Confirms the file went through PHP's upload mechanism before any processing |
| File permissions | Saved files are set to 0644; directories are created as 0755
|
| Overwrite protection | Enabled by default; conflicts are resolved with auto-incrementing suffixes |
Errors from both validation and save operations are collected in an internal list.
$result = $upload->maxMB(1)->to('/uploads')->save();
if ($result === false) {
// Single error
echo $upload->getFirstError();
// All errors
foreach ($upload->getErrors() as $error) {
echo "- $error\n";
}
}$upload = Upload::fromInput('cover_image');
if (!$upload) {
exit('Please select an image.');
}
$filename = $upload
->maxMB(4)
->allowExtension('jpg, jpeg, png, webp')
->allowMimeType('image/jpeg, image/png, image/webp')
->to(BASE_PATH . '/public/images/covers')
->useRandomName()
->save();
if ($filename) {
$imageUrl = '/images/covers/' . $filename;
}$upload = Upload::fromInput('resume');
$saved = $upload
->maxMB(8)
->allowExtension('pdf')
->allowMimeType('application/pdf')
->to('/private/storage/resumes')
->asName('resume_' . $userId)
->save();$uploads = Upload::fromInput('gallery[]') ?? [];
$saved = [];
$errors = [];
foreach ($uploads as $upload) {
$result = $upload
->maxMB(5)
->allowExtension('jpg, png')
->allowMimeType('image/jpeg, image/png')
->to('/uploads/gallery')
->useRandomName()
->save();
if ($result) {
$saved[] = $result;
} else {
$errors[$upload->getOriginalName()] = $upload->getFirstError();
}
}
echo count($saved) . " file(s) uploaded successfully.\n";
foreach ($errors as $name => $error) {
echo "[$name] $error\n";
}try {
$filename = Upload::fromInput('document')
?->maxMB(20)
->allowExtension('pdf, docx')
->to('/storage/docs')
->useRandomName()
->save(throwOnError: true);
} catch (Exception $e) {
// Log or display the error
error_log($e->getMessage());
}