Quando precisamos fazer o upload de uma imagem, geralmente temos duas opções:
- 1 – Copiar a imagem em uma pasta no servidor e salvar apenas o caminho no banco de dados, como é feito nesse artigo.
- 2 – Converter a imagem para binário e salvá-la diretamente no banco de dados.
O primeiro método é o mais comum, pois é mais fácil de ser implementado, além de ter melhor desempenho. Porém, em alguns casos torna-se necessário salvar a imagem diretamente no banco de dados, e assim recorremos ao segundo método. A desvantagem é que é um pouco mais trabalhoso de ser implementado e o desempenho é menor se comparado ao primeiro método, pois será necessário converter a imagem de binário para seu formato original toda vez que for necessário mostrá-la ao usuário.
Nesse artigo veremos como implementar o segundo método. Criaremos um cadastro de fotos sem contexto, apenas para demostrar a ideia, vamos nessa?
Confira o exemplo em funcionamento, clicando aqui.
Banco de dados
Primeiramente, vamos criar nossa tabela.
CREATE TABLE `fotos` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `nome` varchar(60) NOT NULL, `conteudo` mediumblob NOT NULL, `tipo` varchar(20) NOT NULL, `tamanho` int(10) unsigned NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB;
- Conteúdo: campo do tipo BLOB (utilizado para armazenar informações de arquivos em formato binário) e será utilizado para armazenar a foto;
- Tipo: será utilizado para armazenar o tipo do arquivo, pois no nosso exemplo será permitido fazer upload de diferentes tipos (jpg, gif, png, bmp) e no momento de exibir a imagem iremos utilizá-lo;
- Tamanho: O tamanho do arquivo em bytes.
O formulário de cadastro
Em seguida, vamos criar o formulário para cadastrar uma foto.
<form id="formulario" action="ajax/salvar.php" method="post"> <div id="mensagem"></div> <div class="form-group"> <label>Foto</label> <input class="form-control" type="file" name="foto"/> </div> <input id="salvar" class="btn btn-primary" type="submit" value="Salvar" data-loading-text="Salvando..."/> </form>
O lado cliente
Para ficar mais interessante, vamos fazer o envio desse formulário utilizando AJAX.
// Quando enviado o formulário $('#formulario').on('submit', function () { // Armazenando objetos em variáveis para utilizá-los posteriormente var formulario = $(this); var botao = $('#salvar'); var mensagem = $('#mensagem'); // Exibindo indicador de carregamento (Bootstrap) // Docs: http://getbootstrap.com/javascript/#buttons botao.button('loading'); // Enviando formulário $(this).ajaxSubmit({ // Definindo tipo de retorno do servidor dataType: 'json', // Se a requisição foi um sucesso success: function (retorno) { // Se cadastrado com sucesso if (retorno.sucesso) { // Definindo estilo da mensagem (sucesso) mensagem.attr('class', 'alert alert-success'); // Limpando formulário formulario.resetForm(); } else { // Definindo estilo da mensagem (erro) mensagem.attr('class', 'alert alert-danger'); } // Exibindo mensagem mensagem.html(retorno.mensagem); // Escondendo indicador de carregamento botao.button('reset'); }, // Se houver algum erro na requisição error: function () { // Definindo estilo da mensagem (erro) mensagem.attr('class', 'alert alert-danger'); // Exibindo mensagem mensagem.html(retorno.mensagem); // Escondendo indicador de carregamento botao.button('reset'); } }); // Retorna FALSE para que o formulário não seja enviado de forma convencional return false; });
Uma explicação mais detalhada:
- Linha 5, 6 e 7: Apenas armazenamos os objetos da página para ficar mais fácil e rápido de utilizá-los posteriormente. Por exemplo, ao invés de utilizar
$("#mensagem").attr()
, utilizamosmensagem.attr()
; - Linha 11: Utilizando um recurso do Bootstrap, exibimos a mensagem de carregamento definida na propriedade
data-loading-text
do botão, para que assim o usuário saiba que a foto está sendo enviada; - Linha 14: Utilizando o JQuery Form Plugin, fazemos o envio do formulário;
- Linha 20: Se a requisição foi recebida e processada pelo servidor com sucesso, esse método será chamado;
- Linha 23: O servidor irá retornar um JSON com as propriedades sucesso e mensagem (veremos isso mais adiante). Portanto, verificamos se a propriedade sucesso é verdadeira, que significa que a foto foi salva com sucesso no banco de dados;
- Linha 25 e 28: Utilizamos um estilo do Bootstrap para a mensagem e limpamos o formulário para que o usuário possa enviar outra imagem;
- Linha 36 e 39: Já definido o estilo da mensagem (sucesso ou erro), exibimos a mensagem retornada pelo servidor e então escondemos o indicador de carregamento do botão, pois o processamento acabou;
- Linha 44 até 54: Esse método somente será chamado caso ocorra algum erro no servidor. Definimos o estilo de erro, exibimos uma mensagem genérica de erro e escondemos o indicador de carregamento.
O lado servidor
Conexão
Arquivo responsável pela conexão com o banco de dados utilizando PDO.
<?php // Informações para conexão $host = 'localhost'; $usuario = 'root'; $senha = 'root'; $banco = 'rafaelcouto'; $dsn = "mysql:host={$host};port=3306;dbname={$banco}"; try { // Conectando $pdo = new PDO($dsn, $usuario, $senha); } catch (PDOException $e) { // Se ocorrer algum erro na conexão die($e->getMessage()); }
Caso você esteja se perguntando porque de não utilizar a função mysql_connect, recomendo a leitura dessa página.
Utilidade
Nesse arquivo vamos apenas criar uma função para facilitar a mensagem de retorno da requisição AJAX.
<?php // Função que retorno um JSON com a propriedade sucesso e mensagem function retorno($mensagem, $sucesso = false) { // Criando vetor com a propriedades $retorno = array(); $retorno['sucesso'] = $sucesso; $retorno['mensagem'] = $mensagem; // Convertendo para JSON e retornando return json_encode($retorno); }
Salvar
Em seguida, criaremos o arquivo PHP responsável por receber, validar e incluir a foto no banco de dados.
<?php // Incluindo arquivo de conexão require_once('../config/conn.php'); // Funções de utilidade require_once('../funcs/util.php'); // Constantes define('TAMANHO_MAXIMO', (2 * 1024 * 1024)); // Verificando se selecionou alguma imagem if (!isset($_FILES['foto'])) { echo retorno('Selecione uma imagem'); exit; } // Recupera os dados dos campos $foto = $_FILES['foto']; $nome = $foto['name']; $tipo = $foto['type']; $tamanho = $foto['size']; // Validações básicas // Formato if(!preg_match('/^image\/(pjpeg|jpeg|png|gif|bmp)$/', $tipo)) { echo retorno('Isso não é uma imagem válida'); exit; } // Tamanho if ($tamanho > TAMANHO_MAXIMO) { echo retorno('A imagem deve possuir no máximo 2 MB'); exit; } // Transformando foto em dados (binário) $conteudo = file_get_contents($foto['tmp_name']); // Preparando comando $stmt = $pdo->prepare('INSERT INTO fotos (nome, conteudo, tipo, tamanho) VALUES (:nome, :conteudo, :tipo, :tamanho)'); // Definindo parâmetros $stmt->bindParam(':nome', $nome, PDO::PARAM_STR); $stmt->bindParam(':conteudo', $conteudo, PDO::PARAM_LOB); $stmt->bindParam(':tipo', $tipo, PDO::PARAM_STR); $stmt->bindParam(':tamanho', $tamanho, PDO::PARAM_INT); // Executando e exibindo resultado echo ($stmt->execute()) ? retorno('Foto cadastrada com sucesso', true) : retorno($stmt->errorInfo());
Algumas observações:
- Linha 9: Criamos uma constante com o tamanho máximo da foto em bytes, no caso 2 MB;
- Linha 40: Através da função file_get_contents() pegamos o conteúdo do arquivo, no caso como é uma imagem, será retornado o conteúdo binário dela;
- Linha 43 até 49: Montamos o nosso comando SQL e passamos as informações da foto nos parâmetros. Vale ressaltar que devemos utilizar o tipo
PDO::PARAM_LOB
para o conteúdo pois a coluna no banco de dados é do tipo BLOB; - Linha 52: Executamos o comando SQL e testamos o resultado com o operador ternário do PHP; se for executado com sucesso retornamos o JSON
{"sucesso":true,"mensagem":"Foto cadastrada com sucesso"}
com a ajuda da função retorno() que foi criada anteriormente, caso contrário será retornado a mensagem de erro. Vale ressaltar que em ambiente de produção o ideal seria registrar essa mensagem em um arquivo de log e retornar uma mensagem de erro genérica ao usuário;
Convertendo imagem
Com os procedimento acima já conseguimos incluir as fotos em nosso banco de dados. Agora precisamos mostrá-las ao usuário, para isso é necessário converter o conteúdo binário para arquivo. O seguinte script será responsável por isso.
<?php // Incluindo arquivo de conexão require_once('config/conn.php'); $id = (int) $_GET['id']; // Selecionando fotos $stmt = $pdo->prepare('SELECT conteudo, tipo FROM fotos WHERE id = :id'); $stmt->bindParam(':id', $id, PDO::PARAM_INT); // Se executado if ($stmt->execute()) { // Alocando foto $foto = $stmt->fetchObject(); // Se existir if ($foto != null) { // Definindo tipo do retorno header('Content-Type: '. $foto->tipo); // Retornando conteudo echo $foto->conteudo; } }
Em resumo, recuperamos o ID da foto que é passado pelo método GET e buscamos o conteúdo e tipo da foto no banco de dados. Se encontrado a foto, utiliza a função header() para informar ao navegador que a saída desse script vai ser o tipo de arquivo da foto. Por fim, retorna o conteúdo da foto.
Dessa forma, podemos mostrar a imagem simplesmente incluindo uma tag img
passando o ID da imagem, por exemplo:
<img src="imagem.php?id=1" />
Consulta
Para finalizar, vamos fazer uma consulta que vai buscar as fotos no banco de dados e mostrar para o usuário.
<?php // Incluindo arquivo de conexão require_once('config/conn.php'); // Selecionando fotos $stmt = $pdo->query('SELECT id, nome, tipo, tamanho FROM fotos'); ?>
<div class="row"> <?php while ($foto = $stmt->fetchObject()): ?> <div class="col-sm-6 col-md-4"> <div class="thumbnail"> <img src="imagem.php?id=<?php echo $foto->id ?>" /> <div class="caption"> <strong>Nome:</strong> <?php echo $foto->nome ?> <br/> <strong>Tipo:</strong> <?php echo $foto->tipo ?> <br/> <strong>Tamanho:</strong> <?php echo $foto->tamanho ?> bytes <br/> </div> </div> </div> <?php endwhile ?> </div>
Basicamente, selecionamos as fotos cadastradas no banco de dados, percorremos elas e utilizando o script imagem.php que criamos anteriormente carregamos cada foto. Para ficar com uma aparência melhor, utilizamos o recurso de Thumbnail do Bootstrap.
Conclusão
O objetivo deste artigo foi apresentar uma solução para quem necessita gravar fotos no banco de dados utilizando PHP e MySQL. É bom lembrar que em um ambiente de produção o ideal seria reduzir o tamanho da imagem caso seja muito grande e gerar uma miniatura também, mas isso é assunto para outro artigo.
Outro detalhe é que esse método é válido para qualquer tipo de arquivo, só muda a validação, pois o que importa na hora da conversão é o tipo do arquivo.
Particularmente não recomento esse método, pois como foi dito no inicio, o desempenho normalmente é menor e a quantidade de fotos impacta no tamanho do banco de dados. Porém em determinados casos é uma boa saída. Portanto, se essa for sua necessidade, espero que este artigo tenha sido útil para você.
Código fonte disponível em: https://github.com/rafaelcouto/salvar-imagem-no-banco-de-dados-com-php-mysql