data types and standard library
Fileinfo and MIME Detection
Fileinfo helps PHP inspect a file's contents and guess its MIME type. That matters when users upload files, admins import documents, or an application decides how a stored file should be served.
Do not trust the browser-provided filename, file extension, or Content-Type header on its own. They are useful hints, but they can be wrong or deliberately misleading.
Detect a file's MIME type
Use finfo with FILEINFO_MIME_TYPE when you need the detected media type.
<?php
declare(strict_types=1);
$path = tempnam(sys_get_temp_dir(), 'mime_');
file_put_contents($path, "Plain text upload\n");
$info = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $info->file($path);
echo $mimeType . PHP_EOL;
unlink($path);
// Prints:
// text/plain
The detected MIME type comes from the file data, not from the name report.txt.
Use an allowlist
File validation should usually ask, "is this one of the types we accept?" rather than "is this not obviously dangerous?"
<?php
declare(strict_types=1);
function assertAllowedMimeType(string $path, array $allowedMimeTypes): string
{
$info = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $info->file($path);
if (!is_string($mimeType) || !in_array($mimeType, $allowedMimeTypes, true)) {
throw new InvalidArgumentException('File type is not allowed.');
}
return $mimeType;
}
$path = tempnam(sys_get_temp_dir(), 'mime_');
file_put_contents($path, "Name,Email\nNia,nia@example.com\n");
echo assertAllowedMimeType($path, ['text/plain', 'text/csv']) . PHP_EOL;
unlink($path);
// Prints:
// text/plain
Different systems may detect small text-like files as text/plain even when the extension is .csv. Allowlists sometimes need to account for that.
Extensions are still useful
MIME detection and extension checks answer different questions. MIME detection tells you what the content appears to be. The extension affects user expectations, storage names, and download behaviour.
<?php
declare(strict_types=1);
function extensionFromOriginalName(string $filename): string
{
return strtolower(pathinfo($filename, PATHINFO_EXTENSION));
}
echo extensionFromOriginalName('Quarterly-Report.PDF') . PHP_EOL;
// Prints:
// pdf
Use the extension as part of a policy, not as proof that the file is safe.
Validate image uploads beyond MIME type
For images, MIME detection is useful but not the whole story. You may also need dimensions, file size, and image decoding checks.
<?php
declare(strict_types=1);
function assertImageSize(array $size): void
{
[$width, $height] = $size;
if ($width < 1 || $height < 1 || $width > 4000 || $height > 4000) {
throw new InvalidArgumentException('Image dimensions are not allowed.');
}
}
assertImageSize([1200, 800]);
echo 'Image size accepted' . PHP_EOL;
// Prints:
// Image size accepted
In a real upload handler, the size array might come from getimagesize() after the file has passed basic upload and MIME checks.
Keep uploaded files out of public execution paths
Validation is only one part of file safety. Store uploads outside directories where PHP can execute scripts, generate a safe storage name, and serve downloads through controlled application code or static storage with the correct headers.
<?php
declare(strict_types=1);
function storageName(string $extension): string
{
$safeExtension = strtolower($extension);
if (!preg_match('/^[a-z0-9]+$/', $safeExtension)) {
throw new InvalidArgumentException('Invalid extension.');
}
return bin2hex(random_bytes(16)) . '.' . $safeExtension;
}
$name = storageName('pdf');
echo strlen($name) > 4 ? 'generated' : 'invalid';
echo PHP_EOL;
// Prints:
// generated
Do not keep the user's original filename as the storage path. Store it as metadata if you need it for display.
What to remember
Fileinfo is a boundary tool. Use it with an allowlist, combine it with extension and size checks where appropriate, avoid public executable upload paths, and keep useful validation errors without exposing sensitive server paths.
Practice
Task: Validate an uploaded document type
Write a small validator for a document upload represented by a temporary file path and an original filename.
Requirements
- Use
declare(strict_types=1);. - Create a temporary file containing plain text.
- Detect the MIME type with
finfo. - Allow only
text/plainandtext/csv. - Extract and normalise the original filename extension.
- Allow only
txtandcsvextensions. - Return the detected MIME type and extension.
- Show one accepted example and one rejected example.
- Include the expected output as comments in the same PHP code block.
The example should not trust the original filename alone.
Show solution
<?php
declare(strict_types=1);
function validateDocumentUpload(string $path, string $originalName): array
{
$info = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $info->file($path);
if (!is_string($mimeType) || !in_array($mimeType, ['text/plain', 'text/csv'], true)) {
throw new InvalidArgumentException('Document MIME type is not allowed.');
}
$extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
if (!in_array($extension, ['txt', 'csv'], true)) {
throw new InvalidArgumentException('Document extension is not allowed.');
}
return [
'mimeType' => $mimeType,
'extension' => $extension,
];
}
$path = tempnam(sys_get_temp_dir(), 'upload_');
file_put_contents($path, "sku,name\nKB-101,Keyboard\n");
$accepted = validateDocumentUpload($path, 'products.csv');
echo $accepted['mimeType'] . ' / ' . $accepted['extension'] . PHP_EOL;
try {
validateDocumentUpload($path, 'products.php');
} catch (InvalidArgumentException $exception) {
echo $exception->getMessage() . PHP_EOL;
}
unlink($path);
// Prints:
// text/plain / csv
// Document extension is not allowed.
The validator checks the detected content type and the user-facing extension. In a real upload flow, this would sit alongside upload error handling, file size limits, safe storage names, and storage outside executable public paths.