segunda-feira, 19 de setembro de 2011

Backup de um banco de dados MySQL utilizando PHP

Fazer o backup do banco de dados é uma tarefa essencial para recuperar o sistema após um crash de hardware, ataque hacker, invasão alienígena etc...

Pensando nisso estou colocando nas minhas aplicações um botão de backup para que o usuário final possa fazer ele mesmo um backup do banco de dados e salvar em um Pendrive, mandar por e-mail para ele mesmo, ou mandar imprimir em formulário contínuo pra ver se chega na lua, sei lá.

Procurei um bom tanto na internet não achei algo que fosse 100% o que eu queria mas achei algo próximo que me ajudou a lot em http://www.devmedia.com.br/post-3925-Fazendo-backup-de-Mysql-atraves-de-PHP.html

Problema: se um usuário colocasse uma string com um ' no meio, ele fecha a string do sql que usa ' para delimitar a string.

Para isso escapamos as single-quotes ou aspas simples ' com um backslash ou barra invertida \ antes.

Um erro parecido acontece se no banco houver uma string que termina com backslash, pois isso anularia a single quote que fecha a string, então antes de backslash, deve-se colocar outro backslash.


Sem entrar em maiores detalhes sobre ordem dos fatores fiz uma outra adção para quem usa UTF-8 e quer validar os caracteres, erros com importações mal feitas podem ocasionar alguns erros na geração do script esse $checkUtf, se true remove caracteres que não sejam UTF-8 evitando esse pau.

Segue o código, com certeza aceito comentários e sugestões de melhoria.

<?php

// código baseado em http://www.devmedia.com.br/post-3925-Fazendo-backup-de-Mysql-atraves-de-PHP.html
// com melhorias dos comentários do mesmo post
// com adicional de escape string por Marcos Fedato marcosfedato.blogspot.com.br
// com checagem de caracteres UTF-8 por http://stackoverflow.com/questions/1401317/remove-non-utf8-characters-from-string

    $usuario = "user";
    $senha = "123";
    $dbname = "mydb";
    // use true se quiser remover caracteres que não sejam utf-8
    $checkUtf = false;
   
// conectando ao banco
    mysql_connect("localhost", $usuario, $senha) or die(mysql_error());
    mysql_select_db($dbname) or die(mysql_error());

// gerando um arquivo sql. Como?
// a função fopen, abre um arquivo, que no meu caso, será chamado como: nomedobanco.sql
// note que eu estou concatenando dinamicamente o nome do banco com a extensão .sql.
    $back = fopen($dbname . ".sql", "w");

// aqui, listo todas as tabelas daquele banco selecionado acima
    $res = mysql_list_tables($dbname) or die(mysql_error());

    // ultra importante para não dar erro nos primeiros inserts
    // principalmente de usar InnoDB e relacionar as tabelas
    fwrite($back, "set foreign_key_checks=0;\n\n");
   
    // regex para ver se o char é UTF-8
    // Link: http://stackoverflow.com/questions/1401317/remove-non-utf8-characters-from-string
    $regex1 = <<<'END'
/
  ( [\x00-\x7F]                 # single-byte sequences   0xxxxxxx
  | [\xC0-\xDF][\x80-\xBF]      # double-byte sequences   110xxxxx 10xxxxxx
  | [\xE0-\xEF][\x80-\xBF]{2}   # triple-byte sequences   1110xxxx 10xxxxxx * 2
  | [\xF0-\xF7][\x80-\xBF]{3}   # quadruple-byte sequence 11110xxx 10xxxxxx * 3
  )
| .                             # anything else
/x
END;
   
// resgato cada uma das tabelas, num loop
    while ($row = mysql_fetch_row($res))
    {
        $table = $row[0];
// usando a função SHOW CREATE TABLE do mysql, exibo as funções de criação da tabela,
// exportando também isso, para nosso arquivo de backup
        $res2 = mysql_query("SHOW CREATE TABLE $table");
// digo que o comando acima deve ser feito em cada uma das tabelas
        while ($lin = mysql_fetch_row($res2)) {
// instruções que serão gravadas no arquivo de backup
            fwrite($back, "\n#\n# Criação da Tabela : $table\n#\n\n");
            fwrite($back, "$lin[1] ;\n\n#\n# Dados a serem incluídos na tabela\n#\n\n");

// seleciono todos os dados de cada tabela pega no while acima
// e depois gravo no arquivo .sql, usando comandos de insert
            $res3 = mysql_query("SELECT * FROM $table");
            $first = true;
            while ($r = mysql_fetch_row($res3))
            {
                if ($first)
                {
                    $sql = "INSERT INTO $table VALUES ";
                    $first = false;
                }
                else
                {
                    $sql .= ',';
                }
               
               
                $sql .= "('";
               
                $imploded = '';
               
                $firstImplode = true;
               
                foreach ($r as $reg)
                {
                    if ($firstImplode)
                    {
                        $firstImplode = false;
                    }
                    else
                    {
                        $imploded .= "', '";
                    }
                   
                    if ($checkUtf)
                    {
                        $escaped = str_replace('\'', "\\'", str_replace('\\', "\\\\", preg_replace($regex1, '$1', $reg)));
                    }
                    else
                    {
                        $escaped = str_replace('\'', "\\'", str_replace('\\', "\\\\", $reg));
                    }
                    $imploded .= $escaped;
                }
               
                $sql .= $imploded;
               
                $sql .= "')\n";

            }
            if (!$first)
            {
                $sql .= ";\n";
                fwrite($back, $sql);
            }
        }
    }

// fechar o arquivo que foi gravado
    fclose($back);
// gerando o arquivo para download, com o nome do banco e extensão sql.
    $arquivo = $dbname . ".sql";
    Header("Content-type: application/sql");
    Header("Content-Disposition: attachment; filename=$arquivo");
// lê e exibe o conteúdo do arquivo gerado
    readfile($arquivo);
?>