An uploaded file is untrusted binary input plus client-supplied metadata. A safe handler checks the transport result, enforces its own size limit, detects content on the server, generates the stored name, and keeps the file away from executable public directories.
After this lesson, you can build the required multipart form, distinguish upload transport failures from validation failures, and move an accepted temporary file into controlled storage.
The browser sends file bytes only when the form uses POST and multipart/form-data. The input name becomes the key in $_FILES.
<form method="post" enctype="multipart/form-data">
<label for="document">PDF document</label>
<input id="document" name="document" type="file" accept="application/pdf" required>
<button type="submit">Upload</button>
</form>
The accept attribute helps the chooser but is not a security check; the server must verify the received file.
Check that the expected entry exists, that error is a scalar integer, and that it equals UPLOAD_ERR_OK before reading tmp_name. Oversized requests can be rejected before normal form fields reach PHP.
<?php
$upload = $_FILES['document'] ?? null;
if (!is_array($upload) || !isset($upload['error']) || is_array($upload['error'])) {
throw new RuntimeException('Invalid upload structure.');
}
if ($upload['error'] !== UPLOAD_ERR_OK) {
throw new RuntimeException('Upload transport failed: ' . $upload['error']);
}
The numeric upload error describes transport handling; field rules such as permitted MIME type come afterward.
Do not trust $_FILES type or the original extension. Enforce an application size limit and use Fileinfo to detect the temporary file content. Keep a small allowlist that maps accepted MIME types to server-chosen extensions.
<?php
if (($upload['size'] ?? 0) > 5 * 1024 * 1024) {
throw new RuntimeException('File exceeds 5 MiB.');
}
$mime = (new finfo(FILEINFO_MIME_TYPE))->file($upload['tmp_name']);
if ($mime !== 'application/pdf') {
throw new RuntimeException('Only PDF files are accepted.');
}
Application validation remains necessary even when php.ini also defines upload_max_filesize and post_max_size.
Generate a random filename, store outside the public document root when possible, and serve downloads through an authorization check. Never reuse the client filename as a path.
<?php
$name = bin2hex(random_bytes(16)) . '.pdf';
$destination = __DIR__ . '/../storage/uploads/' . $name;
if (!move_uploaded_file($upload['tmp_name'], $destination)) {
throw new RuntimeException('Could not store the uploaded file.');
}
move_uploaded_file() verifies that the source came through PHP HTTP upload handling.
0 of 2 checked
Try this next
0 of 2 completed
Explore 500+ free tutorials across 20+ languages and frameworks.