File upload and $_FILES
File uploads feel different because this is the first time PHP is handling something that comes directly from the browser, not from code you fully control. When a user submits a form with a file, PHP does not save that file where you want it. PHP first stores it in a temporary directory and exposes information about it through the $_FILES superglobal. At this stage, nothing permanent has happened yet.
$_FILES is just an array describing what PHP received. It contains the original filename, a temporary file path, the file size, an upload error code, and a MIME type reported by the browser. The file exists only in PHP’s temp directory. If the request ends and you do nothing, that file is automatically deleted.
A file only becomes truly uploaded when you explicitly move it using move_uploaded_file(). That function is the single gate between “PHP received something” and “this file now lives on my server.” Everything else in an upload system exists to help you decide whether calling that function is allowed.
A proper upload flow always follows the same mental model. First, you inspect what arrived by reading $_FILES. Then, you validate it. Then, and only then, you move it to a permanent location. Skipping or reordering these steps is what makes uploads dangerous.
On the HTML side, uploads require a form with method=”post” and enctype=”multipart/form-data”. Without that encoding type, the browser will not send the file data at all. On the PHP side, uploads only appear in $_FILES, never in $_POST.
Before moving a file, you must always check for upload errors. PHP assigns an error code to every upload attempt, and UPLOAD_ERR_OK is the only acceptable value. If an error exists, the file should be rejected immediately.
After that, you validate what the file actually is. File extensions alone are not trustworthy. MIME types sent by the browser are hints, not guarantees. Safer checks involve limiting allowed extensions, checking MIME types with server-side functions, and enforcing file size limits. The goal is not perfection but reducing risk as much as possible.
You should never trust the original filename. Filenames can contain strange characters, collisions, or even attempts to escape directories. A safe system generates its own filenames and stores files outside public directories when possible.
Finally, if and only if all checks pass, you call move_uploaded_file() with the temporary path from $_FILES and your chosen destination path. That moment is the point of no return. From there on, the file is your responsibility.
Once you understand this flow, file uploads stop being scary. They become just another controlled input. The danger is not in $_FILES itself, but in moving files without thinking. Master the inspection, validation, and move sequence, and you master uploads.