Upload de arquivos em PHP
Fotos de perfil, anexos de currículos, compartilhamento de imagens — todo aplicativo web de verdade lida com o envio de arquivos. O PHP torna isso simples, mas um único detalhe de segurança que passe despercebido pode abrir uma falha grave.
1. Pré-requisitos
(1) Configuração necessária do formulário HTML
<!-- The three requirements for a file upload form -->
<form method="POST" enctype="multipart/form-data">
<input type="file" name="avatar">
<button type="submit">Upload</button>
</form>
| Requisito | Por que |
|---|---|
method="POST" |
Os arquivos devem ser enviados via POST |
enctype="multipart/form-data" |
Obrigatório! Sem isso, apenas o nome do arquivo é enviado |
type="file" |
Indica ao navegador para exibir um seletor de arquivos |
enctype="multipart/form-data" é o erro número um ao fazer upload de arquivos — o PHP recebe o nome do arquivo, mas nunca o conteúdo do arquivo.
(2) Criar o diretório de upload
Dentro da pasta myphp/, crie uma pasta uploads/ e certifique-se de que o PHP tenha permissão de gravação nela:
<?php
// Automatically create the upload directory from your script
$uploadDir = __DIR__ . '/uploads/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
?>
2. $_FILES em detalhes
<?php
// After uploading a file, $_FILES is structured like this:
print_r($_FILES['avatar']);
/*
Array (
[name] => photo.jpg // Original filename
[type] => image/jpeg // MIME type reported by the browser
[tmp_name] => C:\xampp\tmp\php123.tmp // Temporary path on the server
[error] => 0 // Error code (0 = success)
[size] => 85469 // File size in bytes
)
*/
?>
| Chave | Significado | Observação |
|---|---|---|
name |
Nome do arquivo original | Não é confiável — os usuários podem falsificá-lo |
type |
Tipo MIME | Informado pelo navegador, não confiável |
tmp_name |
Arquivo temporário do servidor | Excluído automaticamente ao término do script |
error |
Código de erro | 0 = sucesso; qualquer outro valor = problema |
size |
Tamanho em bytes | Útil para impor limites de tamanho |
(1) Códigos de erro
| Código | Constante | Significado |
|---|---|---|
| 0 | UPLOAD_ERR_OK |
✅ Envio bem-sucedido |
| 1 | UPLOAD_ERR_INI_SIZE |
Exceder o valor definido no arquivo php.ini upload_max_filesize |
| 2 | UPLOAD_ERR_FORM_SIZE |
Excede o MAX_FILE_SIZE do formulário |
| 3 | UPLOAD_ERR_PARTIAL |
O arquivo foi enviado apenas parcialmente |
| 4 | UPLOAD_ERR_NO_FILE |
Nenhum arquivo foi selecionado |
| 6 | UPLOAD_ERR_NO_TMP_DIR |
Nenhuma pasta temporária configurada |
| 7 | UPLOAD_ERR_CANT_WRITE |
Falha na gravação no disco |
3. move_uploaded_file() — A função do núcleo
O PHP armazena os arquivos enviados em um diretório temporário. É preciso movê-los explicitamente para um local permanente; caso contrário, eles serão perdidos quando o script terminar:
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$file = $_FILES['avatar'];
$uploadDir = __DIR__ . '/uploads/';
if ($file['error'] !== UPLOAD_ERR_OK) {
echo "Upload failed. Error code: " . $file['error'];
} else {
$targetPath = $uploadDir . $file['name'];
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
echo "Upload successful! File saved at: {$targetPath}";
} else {
echo "Failed to move the uploaded file";
}
}
}
?>
▶ Exemplo: Upload básico de arquivos com tratamento de erros
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$file = $_FILES['avatar'];
$uploadDir = __DIR__ . '/uploads/';
if ($file['error'] === UPLOAD_ERR_OK) {
$targetPath = $uploadDir . $file['name'];
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
echo "<p style='color:green'>Upload successful!</p>";
}
} elseif ($file['error'] === UPLOAD_ERR_NO_FILE) {
echo "<p style='color:red'>Please select a file</p>";
} else {
echo "<p style='color:red'>Upload failed. Error code: {$file['error']}</p>";
}
}
?>
<form method="POST" enctype="multipart/form-data">
<input type="file" name="avatar">
<button type="submit">Upload</button>
</form>
4. Validação de segurança (Crítico!)
(1) 4.1 Limite do tamanho do arquivo
<?php
$maxSize = 2 * 1024 * 1024; // 2MB
if ($file['size'] > $maxSize) {
die("File too large — maximum 2MB allowed");
}
?>
(2) 4.2 Lista de extensões autorizadas
<?php
$allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $allowed)) {
die("File type not allowed. Accepted: " . implode(', ', $allowed));
}
?>
(3) 4.3 Gerar um nome de arquivo seguro
<?php
// ❌ Using the user-supplied filename directly is dangerous
// $targetPath = $uploadDir . $file['name'];
// ✅ Generate a unique, safe filename
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$safeName = uniqid('img_') . '.' . $ext;
$targetPath = $uploadDir . $safeName;
?>
▶ Exemplo: Função completa de envio seguro
<?php
function handleUpload(array $file, string $uploadDir): array {
$maxSize = 2 * 1024 * 1024; // 2MB
$allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
// 1. Check for upload errors
if ($file['error'] !== UPLOAD_ERR_OK) {
return ['success' => false, 'message' => 'Upload failed'];
}
// 2. Check file size
if ($file['size'] > $maxSize) {
return ['success' => false, 'message' => 'File must not exceed 2MB'];
}
// 3. Check extension (whitelist)
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $allowed)) {
return ['success' => false, 'message' => 'Only image files are allowed'];
}
// 4. Verify the real file type (Magic Bytes — far more reliable than extension)
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!in_array($mimeType, $allowedMimes)) {
return ['success' => false, 'message' => 'File type mismatch'];
}
// 5. Generate a safe filename
$safeName = uniqid('upload_') . '.' . $ext;
$targetPath = $uploadDir . $safeName;
// 6. Move the file
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
return [
'success' => true,
'message' => 'Upload successful',
'path' => $targetPath,
'name' => $safeName,
];
}
return ['success' => false, 'message' => 'Failed to save the file'];
}
// Usage
$result = handleUpload($_FILES['avatar'], __DIR__ . '/uploads/');
echo $result['message'];
?>
5. Envio de vários arquivos
<form method="POST" enctype="multipart/form-data">
<input type="file" name="photos[]" multiple>
<button type="submit">Upload Multiple</button>
</form>
<?php
$uploadDir = __DIR__ . '/uploads/';
$results = [];
foreach ($_FILES['photos']['name'] as $i => $name) {
// Reassemble into a single-file array
$singleFile = [
'name' => $_FILES['photos']['name'][$i],
'type' => $_FILES['photos']['type'][$i],
'tmp_name' => $_FILES['photos']['tmp_name'][$i],
'error' => $_FILES['photos']['error'][$i],
'size' => $_FILES['photos']['size'][$i],
];
$results[] = handleUpload($singleFile, $uploadDir);
}
foreach ($results as $r) {
echo $r['message'] . "<br>";
}
?>
6. Exibição de imagens enviadas
<?php
// List images in the uploads directory
$images = glob(__DIR__ . '/uploads/*.{jpg,jpeg,png,gif,webp}', GLOB_BRACE);
?>
<h3>Uploaded Images</h3>
<div style="display:flex;flex-wrap:wrap;gap:10px">
<?php foreach ($images as $img): ?>
<img src="uploads/<?= basename($img) ?>" width="150">
<?php endforeach; ?>
</div>
<form method="POST" enctype="multipart/form-data">
<input type="file" name="avatar">
<button type="submit">Upload</button>
</form>
❓ Perguntas Frequentes
P: Qual é a diferença entre
move_uploaded_fileecopy? R:move_uploaded_fileverifica se o arquivo realmente chegou por meio de uma solicitação HTTP POST (segura). Ocopy()simples poderia ser explorado para duplicar arquivos arbitrários no servidor. Sempre usemove_uploaded_file()para uploads.
P: Arquivos grandes continuam falhando. O que está acontecendo? R: Verifique três limites: (1)
upload_max_filesizeno arquivo php.ini (padrão: 2M); (2)post_max_size(deve ser maior que upload_max_filesize); (3) o campo oculto opcionalMAX_FILE_SIZEno formulário. Após alterar o arquivo php.ini, reinicie o Apache/Nginx.
P: O
$_FILES['avatar']['type']é confiável? R: Não. Trata-se do tipo MIME informado pelo navegador, que é muito fácil de falsificar. Use ofinfo_file()para verificar os Magic Bytes reais do arquivo e obter uma detecção precisa.
📖 Resumo
- O formulário deve incluir
enctype="multipart/form-data" $_FILES['field']['tmp_name']é o arquivo temporário — mova-o junto commove_uploaded_file()- Quatro etapas de validação: limite de tamanho → lista de extensões permitidas → verificação de MIME → renomeação segura
finfo_file()verifica o tipo real do arquivo por meio dos “Magic Bytes” — muito mais confiável do que a extensão- O envio de vários arquivos exige a remontagem da estrutura
$_FILES - Regra de segurança: lista de permissões > lista de restrições, gerar novos nomes de arquivos, aplicar limites de tamanho
📝 Exercícios
- Criar um recurso para upload de avatares: limitar a 1 MB, permitir apenas os formatos jpg/png/gif e exibir uma pré-visualização após o upload.
- Adicione mensagens completas de erro ao recurso de upload (arquivo muito grande, tipo incorreto, nenhum arquivo selecionado) e mantenha o estado do formulário em caso de falha.
- Implementar um carregador em massa: carregar até 5 imagens e exibi-las em uma grade de miniaturas após o carregamento.



