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-textdo 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_LOBpara 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