Nesse tutorial aprenderemos como criar um formulário para upload(envio) de arquivos e processar arquivos enviados com segurança em PHP.
Preparando o Formulário
Antes de tudo, o elemento <form> tem que ter o atributo enctype=”multipart/form-data”. Se não usar assim o navegador não poderá fazer upload de arquivos.
<form enctype="multipart/form-data" action="index.php" method="post"> </form>
Dentro do form acima usamos <input> com o type=”file”.
<form enctype="multipart/form-data" action="index.php" method="post"> <input type="file" value="" name="Enviar"> </form>
Esse é o resultado:
Se usarmos o atributo multiple poderemos selecionar mais de um arquivo para envio.
<form enctype="multipart/form-data" action="index.php" method="post"> <input type="file" value="" name="enviar" multiple> </form>
Para permitir que apenas certos tipos de arquivos, usamos o atributo accept, que pode ter os seguintes valores:
- Uma extensão de nome de arquivo:
.jpg
,.pdf
,.txt
- Uma string de tipo MIME
- ou image/* para qualquer arquivo de imagem, video/* para qualquer arquivo de vídeo, audio/* para qualquer arquivo de áudio.
<form enctype="multipart/form-data" action="index.php" method="post"> <input type="file" value="" name="enviar" multiple accept="image/jpeg, image/png"> </form>
Configuração para upload do arquivo PHP.INI
Acima vimos como preparar nosso formulário usando HTML. Aqui, no entanto, começaremos a usar o arquivo php.ini do nosso PHP.
O PHP tem algumas opções importantes que controlam o upload do arquivo. Essas opções estão no arquivo php.ini.
Se estiver usando o xampp no Windows possivelmente seu php.ini estará em C:\xampp\php\php.ini
No Linux poderá está em /etc/php/7.4/apache2/php.ini – substitua 7.4 pela versão do seu PHP.
Temos um artigo falando sobre a localização do php.ini: Onde está o php.ini?
Também podemos usar a função php_ini_loaded_file() para vermos a localização do arrquivo php.ini
Abaixo estão as opções importantes que encontramos dentro do php.ini referentes a envio(upload) de arquivos:
; habilita uploads de arquivos HTTP.
file_uploads=On
; Diretório temporário para arquivos HTTP carregados (usará o padrão do sistema se não ; Especificadas).
upload_tmp_dir=”C:\xampp\tmp”
; Tamanho máximo permitido para arquivos enviados.
upload_max_filesize=2M
; Número máximo de arquivos que podem ser enviados por meio de uma única solicitação
max_file_uploads=20
Vamos falar sobre cada uma:
- file_uploads: A diretiva file_upload deve estar On para permitir o upload de arquivos. O padrão é Ativado.
- upload_max_filesize: O upload_max_filesize especifica o tamanho máximo do arquivo a ser enviado. Por padrão é 2M (MB). Se você receber um erro informando que o arquivo excede upload_max_filesize, será necessário aumentar esse valor.
- upload_tmp_dir: O upload_tmp_dir especifica o diretório que armazena os arquivos carregados temporariamente.
- post_max_size: O post_max_size especifica o tamanho máximo dos dados POST. Como você fará upload de arquivos com a solicitação POST, você precisa ter certeza de que o post_max_size é maior que upload_max_size.
- max_file_uploads: A diretiva max_file_uploads limita o número de arquivos que você pode enviar por vez.
Manipulando uploads de arquivos em PHP
Para acessar as informações de um arquivo carregado, você usa o array $_FILES. Por exemplo, acima o nome para nosso input era “enviar” <input type=”file” name=”enviar”>
Então usamos $_FILES assim:
$_FILES['enviar']
O $_FILE[‘enviar’] é um array associativo que consiste nas seguintes chaves:
- name: é o nome do arquivo carregado.
- type: é o tipo MIME do arquivo de upload, por exemplo, imagem/jpeg para imagem JPEG ou application/pdf para arquivo PDF.
- size: é o tamanho do arquivo carregado em bytes.
- tmp_name: é o arquivo temporário no servidor que armazenou o nome do arquivo carregado. Se o arquivo carregado for muito grande, o tmp_name será “none“.
- error: é o código de erro descrevendo o status do upload, por exemplo, UPLOAD_ERR_OK significa que o arquivo foi carregado com sucesso. Exemplos de status retornados:
UPLOAD_ERR_OK = Arquivo carregado com sucesso
UPLOAD_ERR_INI_SIZE = O arquivo é muito grande para fazer upload
UPLOAD_ERR_FORM_SIZE = O arquivo é muito grande para fazer upload
UPLOAD_ERR_PARTIAL = O arquivo foi carregado apenas parcialmente
UPLOAD_ERR_NO_FILE = Nenhum arquivo foi enviado’
UPLOAD_ERR_NO_FILE => Nenhum arquivo foi enviado’
UPLOAD_ERR_NO_TMP_DIR = Falta pasta temporária no servidor
UPLOAD_ERR_CANT_WRITE = O arquivo falhou ao salvar no disco
UPLOAD_ERR_EXTENSION = Arquivo não permitido para upload para este servidor
Exemplo $_FILES[‘enviar’][‘error’] irá retornar o status.
Quando um arquivo é carregado com sucesso, ele é armazenado em um diretório temporário no servidor. E você pode usar a função move_uploaded_file() para mover o arquivo do diretório temporário para outro.
A função move_uploaded_file() aceita dois argumentos. Essa função aceita dos argumentos: nome do arquivo e o destino.
O nome do arquivo pode ser representado por $_FILES[‘enviar’][‘tmp_name’]
Destino é a pasta para onde o arquivo será movido.
A função move_uploaded_file() retorna true se mover o arquivo com sucesso; caso contrário, ele retorna false.
Medidas de segurança
Todas as informações na variável $_FILES não podem ser confiáveis, exceto o tmp_name. Os hackers podem manipular o $_FILES e enviar o script malicioso para o servidor. Para evitar isso, você precisa validar as informações no $_FILES:
#1 verifique se o nome de entrada do arquivo está na variável $_FILES usando o isset():
if(! isset($_FILES['enviar']) ) { // error }
Acima, o “enviar” é o nome do input.
#2 verifique o tamanho real do arquivo usando a função filesize() e compare seu resultado com o tamanho máximo de arquivo permitido. Ele não deve confiar no tamanho fornecido pelo $_FILES. Por exemplo:
const MAX_SIZE = 5 * 1024 * 1024; // 5MB if (filesize($_FILES['file']['tmp_name']) > MAX_SIZE) { // error }
Lembre-se que MAX_SIZE não deve ser maior que upload_max_filesize especificado no php.ini.
O tamanho de um arquivo está em bytes, o que não é legível por humanos. Para torná-lo mais legível, podemos definir uma função que converte os bytes em um formato legível por humanos.
function format_filesize(int $bytes, int $decimals = 2): string { $units = 'BKMGTP'; $factor = floor((strlen($bytes) - 1) / 3); return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . $units[(int)$factor]; }
#3 valide o tipo MIME do arquivo em relação aos tipos de arquivo permitidos. Para fazer isso, você precisa definir uma lista de arquivos permitidos:
const ALLOWED_FILES = [ 'image/png' => 'png', 'image/jpeg' => 'jpg' ];
Para obter o tipo mime real de um arquivo, você usa três funções: finfo_open(), finfo_file() e finfo_close():
- finfo_open() retorna um novo recurso fileinfo
- finfo_file() retorna as informações sobre o arquivo.
- finfo_close() fecha o recurso fileinfo.
Para torná-lo fácil e reutilizável, você pode definir uma função get_mime_type() assim:
function get_mime_type(string $filename) { $info = finfo_open(FILEINFO_MIME_TYPE); if (!$info) { return false; } $mime_type = finfo_file($info, $filename); finfo_close($info); return $mime_type; }
A função get_mime_type() acima aceita um nome de arquivo e retorna o tipo MIME do arquivo. Ele retornará false se ocorrer um erro. a Internet Assigned Numbers Authority (IANA) é responsável por todos os tipos MIME oficiais, e você pode encontrar a lista completa na MIME type page.
Se ocorrer um erro ou a validação falhar, você poderá definir uma mensagem flash e redirecionar o navegador de volta para a página de upload. A função a seguir define uma mensagem flash e executa um redirecionamento:
function redirect_with_message(string $message, string $type=FLASH_ERROR, string $name='upload', string $location='index.php'): void { flash($name, $message, $type); header("Location: $location", true, 303); exit; }
Observe que usamos a função flash() definida no arquivo flash.php. A função flash() mostra uma mensagem flash baseada em sessão. Confira o tutorial de mensagem flash aqui.
Veja a seguir como usar a função redirection_with_message():
if(error) { redirect_with_message('An error occurred'); }
A instrução return encerra o script atual.
Como todas essas funções get_mime_type(), format_filesize() e redirect_with_message() são reutilizáveis, você pode adicioná-las ao arquivo functions.php assim:
<?php /** * Messages associated with the upload error code */ const MESSAGES = [ UPLOAD_ERR_OK => 'File uploaded successfully', UPLOAD_ERR_INI_SIZE => 'File is too big to upload', UPLOAD_ERR_FORM_SIZE => 'File is too big to upload', UPLOAD_ERR_PARTIAL => 'File was only partially uploaded', UPLOAD_ERR_NO_FILE => 'No file was uploaded', UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder on the server', UPLOAD_ERR_CANT_WRITE => 'File is failed to save to disk.', UPLOAD_ERR_EXTENSION => 'File is not allowed to upload to this server', ]; /** * Return a mime type of file or false if an error occurred * * @param string $filename * @return string | bool */ function get_mime_type(string $filename) { $info = finfo_open(FILEINFO_MIME_TYPE); if (!$info) { return false; } $mime_type = finfo_file($info, $filename); finfo_close($info); return $mime_type; } /** * Return a human-readable file size * * @param int $bytes * @param int $decimals * @return string */ function format_filesize(int $bytes, int $decimals = 2): string { $units = 'BKMGTP'; $factor = floor((strlen($bytes) - 1) / 3); return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . $units[(int)$factor]; } /** * Redirect user with a session based flash message * @param string $message * @param string $type * @param string $name * @param string $location * @return void */ function redirect_with_message(string $message, string $type=FLASH_ERROR, string $name='upload', string $location='index.php'): void { flash($name, $message, $type); header("Location: $location", true, 303); exit; }
Dica: Usando o campo Chamado MAX_FILE_SIZE no input
Se colocarmos um campo e dermos a ele o nome MAX_FILE_SIZE antes input type=”file“, o PHP usará esse valor em vez de upload_max_filesize para validar o tamanho do arquivo.
Vamos a um exemplo.
<form enctype="multipart/form-data" action="upload.php" method="post"> <div> <label for="file">Selecione um Arquivo:</label> <input type="hidden" name="MAX_FILE_SIZE" value="10240"/> <input type="file" id="file" name="file"/> </div> <div> <button type="submit">Upload</button> </div> </form>
Acima, o MAX_FILE_SIZE é 10 KB. Se você carregar um arquivo maior que 10 KB, o PHP emitirá um erro. No entanto, é fácil manipular esse campo, portanto, nunca confie nessa dica para fins de segurança.
Note também que você não pode definir MAX_FILE_SIZE maior que a diretiva upload_max_filesize no arquivo php.ini.
Exemplo Completo de Envio de Arquivo em PHP
Nossa estrutura de Arquivos
Teremos essa estrutura de pastas e arquivos
├── inc | ├── flash.php | └── functions.php ├── index.php ├── upload.php └── uploads
Acima, inc é uma pasta e dentro dela temos os arquivos flash.php e functions.php
Vamos ver o conteúdo que colocaremos dentro de cada arquivo.
index.php
adicione o seguinte formulário de upload de arquivo ao arquivo index.php:
<?php session_start(); require_once __DIR__ . '/inc/flash.php'; ?> <!DOCTYPE html> <html lang="pt"> <head> <meta charset="UTF-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <link rel="stylesheet" href="/app/css/style.css"/> <title>Envio de Arquivos em PHP</title> </head> <body> <?php flash('upload') ?> <main> <form enctype="multipart/form-data" action="upload.php" method="post"> <div> <label for="arquivo">Selecione um Arquivo:</label> <input type="file" id="arquivo" name="arquivo"/> </div> <div> <button type="submit">Enviar</button> </div> </form> </main> </body> </html>
O arquivo index.php também contém um formulário para fazer upload de um arquivo. O arquivo upload.php tratará do upload.
upload.php
adicione o seguinte código ao arquivo upload.php para processar o arquivo carregado:
<?php session_start(); require_once __DIR__ . '/inc/flash.php'; require_once __DIR__ . '/inc/functions.php'; const ARQUIVOS_PERMITIDOS = [ 'image/png' => 'png', 'image/jpeg' => 'jpg' ]; const MAX_SIZE = 5 * 1024 * 1024; // 5MB const UPLOAD_DIR = __DIR__ . '/uploads'; $is_post_request = strtolower($_SERVER['REQUEST_METHOD']) === 'post'; // o método é post? $has_file = isset($_FILES['file']); //has_file significa "existe arquivo?" if (!$is_post_request || !$has_file) { redirect_with_message('Operação de Envio Inválida', FLASH_ERROR); } // $status = $_FILES['file']['error']; $filename = $_FILES['file']['name']; $tmp = $_FILES['file']['tmp_name']; // ocorrencia de erros if ($status !== UPLOAD_ERR_OK) { redirect_with_message($messages[$status], FLASH_ERROR); } // validar tamanho do arquivo $filesize = filesize($tmp); if ($filesize > MAX_SIZE) { redirect_with_message('Erro! o tamanho do arquivo ' . format_filesize($filesize) . ' , e maior que o permitido ' . format_filesize(MAX_SIZE), FLASH_ERROR); } // validate o tipo do arquivo $mime_type = get_mime_type($tmp); if (!in_array($mime_type, array_keys(ALLOWED_FILES))) { redirect_with_message('The file type is not allowed to upload', FLASH_ERROR); } // define o nome do arquivo como base + extensao $uploaded_file = pathinfo($filename, PATHINFO_FILENAME) . '.' . ALLOWED_FILES[$mime_type]; // novo local $filepath = UPLOAD_DIR . '/' . $uploaded_file; // mova para o diretorio upload $success = move_uploaded_file($tmp, $filepath); if ($success) { redirect_with_message('O arquivo foi Enviado com Sucesso.', FLASH_SUCCESS); } redirect_with_message('Erro ao mover arquivo para pasta .', FLASH_ERROR);
Como upload.php funciona? Vamos explicar trechos dele:
#1 ele Inicia uma sessão e inclui os arquivos flash.php e functions.php para usar as funções desses.
session_start(); require_once __DIR__ . '/inc/flash.php'; require_once __DIR__ . '/inc/functions.php';
#2 Define um array que especifica os arquivos permitidos:
const ARQUIVOS_PERMITIDOS = [ 'image/png' => 'png', 'image/jpeg' => 'jpg' ];
#3 Define uma constante que especifica o tamanho máximo do arquivo:
const MAX_SIZE = 5 * 1024 * 1024; // 5MB
#4 Define o diretório de upload que armazena os arquivos enviados:
const UPLOAD_DIR = __DIR__ . '/uploads';
#5 Retorna uma mensagem de erro se o método de solicitação não for POST ou o arquivo não existir na variável $_FILES:
$is_post_request = strtolower($_SERVER['REQUEST_METHOD']) === 'post'; // o método é post? $has_file = isset($_FILES['file']); //has_file significa "existe arquivo?" if (!$is_post_request || !$has_file) { redirect_with_message('Operação de Envio Inválida', FLASH_ERROR); }
#6 Obtém as informações do arquivo carregado, incluindo erro, nome do arquivo e nome do arquivo temporário:
$status = $_FILES['file']['error']; $filename = $_FILES['file']['name']; $tmp = $_FILES['file']['tmp_name'];
#7 Retorne uma mensagem de erro se o upload do arquivo falhou:
if ($status !== UPLOAD_ERR_OK) { redirect_with_message($messages[$status], FLASH_ERROR); }
#8 Obtenha o tamanho do arquivo na pasta temporária e compare-o com o MAX_SIZE. Se o tamanho do arquivo enviado for maior que MAX_SIZE, emita um erro:
// validar tamanho do arquivo $filesize = filesize($tmp); if ($filesize > MAX_SIZE) { redirect_with_message('Erro! o tamanho do arquivo ' . format_filesize($filesize) . ' , e maior que o permitido ' . format_filesize(MAX_SIZE), FLASH_ERROR); }
#9 Obtenha o tipo MIME e compare-o com o tipo MIME dos arquivos permitidos especificados no array ALLOWED_FILES; emitir um erro se a validação falhar:
// valide o tipo do arquivo $mime_type = get_mime_type($tmp); if (!in_array($mime_type, array_keys(ALLOWED_FILES))) { redirect_with_message('The file type is not allowed to upload', FLASH_ERROR); }
#10 Construa um novo nome de arquivo concatenando(juntando) o nome de arquivo do arquivo carregado com a extensão de arquivo válida.
Observe que o pathinfo() retorna o nome do arquivo sem a extensão:
// define o nome do arquivo como base + extensao $uploaded_file = pathinfo($filename, PATHINFO_FILENAME) . '.' . ALLOWED_FILES[$mime_type];
#11 Mova o arquivo do diretório temporário para a pasta de upload e emita uma mensagem de erro ou sucesso dependendo do resultado da função move_uploaded_file():
// novo local $filepath = UPLOAD_DIR . '/' . $uploaded_file; // mova para o diretorio upload $success = move_uploaded_file($tmp, $filepath); if ($success) { redirect_with_message('O arquivo foi Enviado com Sucesso.', FLASH_SUCCESS); } redirect_with_message('Erro ao mover arquivo para pasta .', FLASH_ERROR);
Conclusão
Hoje vimos como enviar(upload) um arquivo e movendo-o de uma pasta para outra, geralmente em servidores, computadores diferentes, remotos.
Comment on “PHP: Trabalhando com Envio(upload) de arquivos”