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

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
🔥 Erro comum: Esquecer 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
<?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
<?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
<?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
<?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>
▶ Experimente

4. Validação de segurança (Crítico!)

(1) 4.1 Limite do tamanho do arquivo

PHP
<?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
<?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));
}
?>
💡 Dica: Sempre use uma lista de permissões (permita apenas tipos conhecidos como seguros), nunca uma lista de restrições (que bloqueia arquivos com extensões como .php, .exe etc.). Os invasores sempre encontrarão uma extensão que sua lista de restrições não tenha incluído.

(3) 4.3 Gerar um nome de arquivo seguro

PHP
<?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
<?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'];
?>
▶ Experimente

5. Envio de vários arquivos

PHP
<form method="POST" enctype="multipart/form-data">
    <input type="file" name="photos[]" multiple>
    <button type="submit">Upload Multiple</button>
</form>
PHP
<?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>";
}
?>
💡 Dica: O PHP armazena vários arquivos em uma estrutura “colunar” — todos os nomes em um array, todos os tamanhos em outro, etc. É preciso reorganizá-los manualmente em arrays por arquivo para o processamento.


6. Exibição de imagens enviadas

PHP
<?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_file e copy? R: move_uploaded_file verifica se o arquivo realmente chegou por meio de uma solicitação HTTP POST (segura). O copy() simples poderia ser explorado para duplicar arquivos arbitrários no servidor. Sempre use move_uploaded_file() para uploads.

P: Arquivos grandes continuam falhando. O que está acontecendo? R: Verifique três limites: (1) upload_max_filesize no arquivo php.ini (padrão: 2M); (2) post_max_size (deve ser maior que upload_max_filesize); (3) o campo oculto opcional MAX_FILE_SIZE no 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 o finfo_file() para verificar os Magic Bytes reais do arquivo e obter uma detecção precisa.

📖 Resumo

📝 Exercícios

  1. 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.
  2. 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.
  3. Implementar um carregador em massa: carregar até 5 imagens e exibi-las em uma grade de miniaturas após o carregamento.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%