
🚀 Carregar vários ficheiros com HTML e PHP
Um utilizador precisa de carregar cinco imagens, mas o formulário só aceita uma. Ou um cliente pede carregamentos de documentos em lote com um título associado a cada ficheiro. Isto não é fantasia: é uma tarefa padrão que o PHP resolve em trinta linhas.
O problema é que a maioria dos tutoriais mostra carregamentos de um único ficheiro. O carregamento múltiplo funciona de forma diferente: um array de ficheiros em vez de um, mapeamento para campos adicionais, proteção contra sobrescrita. Abaixo encontra uma implementação funcional, do zero até um trecho de código pronto a usar.
💡 Visão geral rápida:
- Crie um formulário HTML com
enctype="multipart/form-data"e um campofilecom o atributomultiple. - Processe o array
$_FILESem PHP: percorra os índices e utilizemove_uploaded_file()para cada ficheiro. - Adicione validação de extensão e tamanho antes de gravar em disco.
- Para associar "ficheiro + título", utilize arrays paralelos
title[]efileUpload[].
Um vídeo rápido que aborda o mesmo tópico, desde o formulário HTML até gravar ficheiros numa base de dados:
Passo 1: Formulário HTML para carregar vários ficheiros
Primeiro, construa o formulário. Pontos-chave: o atributo enctype definido como multipart/form-data e um campo de entrada com multiple. Sem o enctype, o servidor simplesmente não receberá ficheiros; isto não é um erro do PHP, mas da especificação HTTP.
Dê ao campo o nome de um array: fileUpload[]. Os parênteses retos no final indicam ao PHP que o servidor está a receber uma lista, e não um único ficheiro.
1 <form action="" method="post" enctype="multipart/form-data"> 2 <label>Select files:</label> 3 <input type="file" name="fileUpload[]" multiple> 4 <input type="submit" name="Submit" value="Upload"> 5 </form>
O formulário acima permite selecionar qualquer número de ficheiros de uma só vez na janela de seleção. O navegador irá empacotá-los num pedido multipart e enviá-los para o servidor. Não é necessário JavaScript nesta fase.
Passo 2: Processamento PHP do array de ficheiros
Quando o formulário é submetido ao servidor, o PHP preenche o array superglobal $_FILES. Mas a estrutura difere dos carregamentos de ficheiro único: as chaves name / tmp_name / size / error tornam-se arrays onde o índice corresponde ao número sequencial do ficheiro.
Percorremos os índices e guardamos cada ficheiro usando move_uploaded_file():
1 <?php 2 3 $target_dir = 'uploads/'; 4 5 if (isset($_FILES['fileUpload']['name'])) { 6 7 $total_files = count($_FILES['fileUpload']['name']); 8 9 for ($key = 0; $key < $total_files; $key++) { 10 11 // Skip empty fields (no file selected) 12 if (isset($_FILES['fileUpload']['name'][$key]) 13 && $_FILES['fileUpload']['size'][$key] > 0) { 14 15 $original_filename = $_FILES['fileUpload']['name'][$key]; 16 $target = $target_dir . basename($original_filename); 17 $tmp = $_FILES['fileUpload']['tmp_name'][$key]; 18 19 move_uploaded_file($tmp, $target); 20 } 21 } 22 }
Adicione este código ao functions.php do seu tema filho ou ao plugin Code Snippets, e o formulário do passo 1 começará a aceitar ficheiros.
Note que basename() remove o caminho do nome do ficheiro. Isto protege contra ataques de path traversal: um atacante poderia enviar ../../wp-config.php como nome. O basename() deixa apenas wp-config.php, mas a próxima camada de defesa deve continuar a ser a validação da extensão.
Passo 3: Verificar o tipo e o tamanho do ficheiro
Não pode aceitar "tal como vem" tudo o que o navegador envia. Proteção mínima: uma lista branca de extensões e um limite de tamanho.
Verificação da extensão usando pathinfo() com a flag PATHINFO_EXTENSION. Converta para minúsculas e compare com as extensões permitidas:
1 $allowed_ext = array('jpg', 'jpeg', 'png', 'gif', 'bmp', 'pdf', 'doc', 'docx'); 2 $ext = strtolower(pathinfo($_FILES['fileUpload']['name'][$key], PATHINFO_EXTENSION)); 3 4 if (!in_array($ext, $allowed_ext)) { 5 $errors[$key] = 'Invalid file type: ' . $ext; 6 continue; 7 }
A verificação do tamanho é ainda mais simples; o valor size em bytes já está em $_FILES:
1 $max_file_size = 5 * 1024 * 1024; // 5 MB 2 3 if ($_FILES['fileUpload']['size'][$key] > $max_file_size) { 4 $errors[$key] = 'File exceeds the allowed size (5 MB)'; 5 continue; 6 }
Coloque ambas as verificações antes de move_uploaded_file() no mesmo ciclo. Os ficheiros que falharem a validação não chegarão ao disco. Recolha os erros num array e, após o processamento, mostre ao utilizador uma lista indicando qual o ficheiro que falhou e porquê. Sem isto, os utilizadores verão apenas um resultado vazio e irão embora, assumindo que o formulário está avariado. Para cada ficheiro rejeitado, indique o nome e o motivo, como "foto.png: tipo inválido" ou "arquivo.zip: tamanho excedido".
Passo 4: Renomear ficheiros durante o carregamento
Se dois utilizadores carregarem photo.jpg, o segundo ficheiro irá sobrescrever o primeiro. A solução é gerar um nome único ao guardar. A abordagem mais simples: substituir espaços por underscores e adicionar time().
1 if (isset($_FILES['fileUpload']['name'][$key]) 2 && $_FILES['fileUpload']['size'][$key] > 0) { 3 4 $original_filename = $_FILES['fileUpload']['name'][$key]; 5 6 // Extension separately 7 $ext = pathinfo($original_filename, PATHINFO_EXTENSION); 8 9 // Name without extension 10 $filename_without_ext = basename($original_filename, '.' . $ext); 11 12 // New name: no spaces + timestamp 13 $new_filename = str_replace(' ', '_', $filename_without_ext) 14 . '_' . time() . '.' . $ext; 15 16 move_uploaded_file( 17 $_FILES['fileUpload']['tmp_name'][$key], 18 $target_dir . $new_filename 19 ); 20 }
O timestamp time() fornece unicidade ao segundo. Em projetos de alto tráfego, substitua-o por uniqid() ou UUID: dois pedidos simultâneos no mesmo segundo são suficientes para causar uma colisão e perder um dos ficheiros. Além disso, time() revela a data de carregamento no nome do ficheiro, o que por vezes é indesejável. Um UUID do ramsey/uuid elimina completamente ambos os problemas.
Passo 5: Carregamento com campos adicionais para cada ficheiro
Por vezes, precisa de anexar um título, descrição ou categoria a cada ficheiro. Em vez de um par "título + ficheiro", cria vários, dando aos campos o nome de arrays:
1 <form action="" method="post" enctype="multipart/form-data"> 2 3 <input type="text" name="title[]" placeholder="Title"> 4 <input type="file" name="fileUpload[]"> 5 6 <input type="text" name="title[]" placeholder="Title"> 7 <input type="file" name="fileUpload[]"> 8 9 <input type="text" name="title[]" placeholder="Title"> 10 <input type="file" name="fileUpload[]"> 11 12 <input type="submit" name="Submit" value="Upload"> 13 14 </form>

Do lado do servidor, $_POST['title'] e $_FILES['fileUpload']['name'] são arrays paralelos. O índice 0 num corresponde ao índice 0 no outro. O processamento usa o mesmo ciclo, mas agora com associação de título:
1 $total_files = count($_FILES['fileUpload']['name']); 2 3 for ($key = 0; $key < $total_files; $key++) { 4 5 if ($_FILES['fileUpload']['size'][$key] === 0) { 6 continue; // empty field — skip 7 } 8 9 $title = isset($_POST['title'][$key]) ? $_POST['title'][$key] : ''; 10 $tmp = $_FILES['fileUpload']['tmp_name'][$key]; 11 $fname = basename($_FILES['fileUpload']['name'][$key]); 12 $target = $target_dir . time() . '_' . $fname; 13 14 if (move_uploaded_file($tmp, $target)) { 15 // Save $title and $target to the database 16 // or into an array for further processing 17 } 18 }
Certifique-se de que o número de campos title[] corresponde ao número de campos fileUpload[] no formulário. Se o utilizador adicionar campos dinamicamente via JavaScript, todos os índices devem ser consecutivos, sem falhas. O PHP não colapsa automaticamente arrays esparsos, pelo que $_POST['title'][5] pode existir enquanto $_FILES['fileUpload']['name'][5] não.
Na prática, esta abordagem cobre uma boa metade das tarefas de clientes: galerias de imagens com legendas, carregamentos de documentos com etiquetas, submissões de currículos com portefólios. O mesmo princípio de arrays paralelos funciona para descrição, categoria, ordem e quaisquer outros campos.
⁉️🤔 Perguntas frequentes
Porque é que o ficheiro não aparece no $_FILES apesar de o formulário ter sido submetido?
Nove em cada dez vezes, falta o
enctype="multipart/form-data"na tag<form>. Sem ele, o navegador envia os dados como texto simples e o PHP não preenche$_FILES. Verifique tambémpost_max_sizeeupload_max_filesizenophp.ini; exceder qualquer um deles resulta num array vazio, sem erros visíveis.
Como limito o tamanho total de carregamento para um único pedido?
A diretiva
post_max_sizenophp.initrunca todo o corpo do pedido POST, incluindo ficheiros e campos de texto. O valor padrão, de acordo com a documentação do PHP, é de 8 MB. Defina-o mais alto do queupload_max_filesize, com uma margem para a codificação multipart (cerca de 20%). Para carregamentos em massa, aumente ambos os parâmetros:upload_max_filesize = 20Mepost_max_size = 25M.
Os ficheiros podem ser carregados de forma assíncrona via JavaScript?
Sim. Use
FormDataefetchcommethod: 'POST'. Adicione camposfileUpload[]usandoformData.append('fileUpload[]', file)para cada ficheiro selecionado. Não defina o cabeçalhoContent-Typemanualmente; o navegador definirá automaticamentemultipart/form-datacom o boundary correto. Para feedback visual, use uma barra de progresso viaXMLHttpRequest.upload.onprogress, uma vez quefetchnão expõe o progresso diretamente.
E quanto à segurança: um atacante carrega um script PHP em vez de uma imagem?
A verificação de extensão do passo 3 é a primeira linha de defesa. Segunda: armazene os ficheiros carregados FORA da pasta raiz do site ou num diretório com um
.htaccessque desative a execução (php_flag engine off). Terceira: renomeie os ficheiros ao guardar (passo 4); mesmo queshell.phppasse o filtro de extensão, torna-seshell_1718300000.php, o que não é perigoso sem permissões de execução. Quarta: verifique o tipo MIME não a partir de$_FILES['type'](enviado pelo navegador, facilmente falsificável), mas viafinfo(Fileinfo integrado do PHP), que lê a assinatura do ficheiro.
O que esta abordagem proporciona: o quadro completo
Construiu um sistema de carregamento que lida com três cenários de imediato: ficheiros em lote com um botão, carregamento único com validação e emparelhamento "ficheiro + metadados" através de arrays paralelos. O código tem cerca de trinta linhas para cada passo, nada mais.
Se estiver a trabalhar em WordPress, coloque o manipulador no functions.php do seu tema filho ou no plugin Code Snippets. Em PHP puro, basta colocar o script de processamento no destino da ação do formulário.
O código deste artigo cobre a maioria das tarefas típicas de carregamento. Os cenários restantes (carregamento em partes para ficheiros grandes, carregamento direto para armazenamento compatível com S3 e arrastar e largar com pré-visualização) serão abordados em artigos separados. Escreva nos comentários qual deles é relevante para o seu projeto, e daremos prioridade a esse.



