php
55 lines · 8 steps
Validating file uploads safely in PHP
A layered set of checks that rejects malformed, oversized, or spoofed uploads before trusting a file.
Explained by
highlit
1<?php
2
3class ImageUploadService
4{
5 private const MAX_BYTES = 5 * 1024 * 1024;
6
7 private const ALLOWED = [
8 'image/jpeg' => 'jpg',
9 'image/png' => 'png',
10 'image/webp' => 'webp',
11 ];
12
13 public function __construct(private string $storageDir) {}
14
15 public function store(array $file): string
16 {
17 if (!isset($file['error']) || is_array($file['error'])) {
18 throw new InvalidArgumentException('Malformed upload payload.');
19 }
20
21 if ($file['error'] !== UPLOAD_ERR_OK) {
22 throw new RuntimeException('Upload failed with code ' . $file['error']);
23 }
24
25 if ($file['size'] > self::MAX_BYTES) {
26 throw new RuntimeException('File exceeds the 5MB limit.');
27 }
28
29 if (!is_uploaded_file($file['tmp_name'])) {
30 throw new RuntimeException('Suspicious upload, not an HTTP upload.');
31 }
32
33 $finfo = new finfo(FILEINFO_MIME_TYPE);
34 $mime = $finfo->file($file['tmp_name']);
35
36 if (!isset(self::ALLOWED[$mime])) {
37 throw new RuntimeException('Unsupported image type: ' . $mime);
38 }
39
40 if (@getimagesize($file['tmp_name']) === false) {
41 throw new RuntimeException('File is not a valid image.');
42 }
43
44 $name = bin2hex(random_bytes(16)) . '.' . self::ALLOWED[$mime];
45 $target = rtrim($this->storageDir, '/') . '/' . $name;
46
47 if (!move_uploaded_file($file['tmp_name'], $target)) {
48 throw new RuntimeException('Could not persist the uploaded file.');
49 }
50
51 chmod($target, 0644);
52
53 return $name;
54 }
55}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Never trust the client-supplied MIME type; detect content type from the file's actual bytes.
- 2Layer cheap structural checks before expensive ones so bad input fails fast.
- 3Generate random server-side filenames to avoid path traversal and collisions from user input.
Related explainers
php
<?php namespace App\Support;
Locale-aware formatting with PHP's intl extension
internationalization
encapsulation
constructor-injection
Intermediate
7 steps
php
<?php namespace App\Support;
Merging query params onto a URL in PHP
url-parsing
query-strings
immutability
Intermediate
8 steps
php
<?php namespace App\View;
Building a safe HTML escaper in PHP
security
xss
escaping
Intermediate
6 steps
ruby
require 'json' require 'set' class SensitiveScrubber
Recursively scrubbing secrets from JSON
recursion
data-masking
pattern-matching
Intermediate
7 steps
php
<?php namespace App\Observers;
How Eloquent observers hook lifecycle events in Laravel
observers
lifecycle-hooks
queues
Intermediate
6 steps
php
<?php namespace App\Rules;
How a custom phone validation rule works in Laravel
validation
custom-rules
dependency
Intermediate
6 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/validating-file-uploads-safely-in-php-explained-php-134c/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.