MYSQL Backup mit PHP

Bilzebub

Mitglied
hi,
da mein Hoster mir die functionen exec und shell_exec verweigert kann ich leider damit kein MYSQL Backup machen. Nun möchte ich mich jedoch nicht immer ins phpMyAdmin begeben um ein Backup meiner Datenbank zu machen (Davon abgesehen: Sollen auch andere Admins ein Backup machen können ohne gleiche meine Passwörter zu kennen ;))
Nun habe ich schon angefangen und mich auch gleich an Klassen versucht!

Hier erstmal das Script:
PHP:
<?php

    class mysql_backup{
        var $host;
        var $database;
        
        function create_db_startcomment(){
            $out = "
                -- //////////////////////////////////////////////////////////////\n 
                -- Host: ".$this->$host."\n
                -- Time: ".date('d.m.Y - H:i')."\n
                -- Database: ".$this->$database."\n
                -- //////////////////////////////////////////////////////////////\n 
            ";
            return $out; 
        }
        
        function create_table_structure(){
            $anfrage1=mysql_query("SHOW TABLE STATUS FROM ".$this->$database."");
            while($row1=mysql_fetch_array($anfrage1)){
                //echo"<pre>";
                //print_r($row1);
                //echo"</pre>";
                $out .= "
                    \n
                    -- //////////////////////////////////////////////////////////\n
                    -- ---- Tabellenstruktur für `".$row1['Name']."`\n
                    -- //////////////////////////////////////////////////////////\n\n
                ";
                $tmp2 = array();
                $out .= "CREATE TABLE ";
                $out .= "IF NOT EXISTS ";
                $out .= "`".$row1[0]."` (\n";
                $anfrage2=mysql_query("SHOW FULL FIELDS FROM `".$row1['Name']."`");
                while($row2=mysql_fetch_array($anfrage2)){
                    //echo"<pre>";
                    //print_r($row2);
                    //echo"</pre>";
                    $tmp .= "`".$row2['Field']."` ".$row2['Type']." ";
                    if($row2['Null']=="NO")               { $tmp .= " NOT NULL";}
                    if($row2['Null']=="YES")              { $tmp .= " NULL";}
                    if($row2['Extra']=="auto_increment")  { $tmp .= " AUTO_INCREMENT";}
                    if($row2['Comment']!="")              { $tmp .= " COMMENT '".$row2['Comment']."'";}
                    $tmp2[] .= $tmp; $tmp="";
                }
                
                $anfrage3=mysql_query("SHOW KEYS FROM `".$row1[0]."`");
                while($row3=mysql_fetch_array($anfrage3)){
                    //echo"<pre>";
                    //print_r($row3);
                    //echo"</pre>";
                    $tmp .= "".$row3['Key_name']." KEY (`".$row3['Column_name']."`)";
                }
                if(!empty($tmp)){ $tmp2[] .= $tmp; $tmp="";}
                
                $out .= implode(",\n",$tmp2);
                $out .= "\n)";
                $out .= " ENGINE=".$row1['Engine']."";
                if($row1['Auto_increment']!="")           { $out .= " AUTO_INCREMENT=".$row1['Auto_increment'];}
                $out .= "";
                $out .= "\n\n";
            }
            return $out;        
        }
    }
    $backup = new mysql_backup;
    $backup -> $host = "";
    $backup -> $database = "";
    $backup_str  = $backup -> create_db_startcomment();
    $backup_str .= $backup -> create_table_structure(); 
    
    echo nl2br($backup_str);

?>

Folgendes erhalte ich wenn ich mit phpMyAdmin ein "Backup" mache:
Code:
CREATE TABLE IF NOT EXISTS `mhpsp_bereiche` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `f1` varchar(50) NOT NULL,
  `f2` char(2) NOT NULL,
  `f3` text NOT NULL,
  `f4` tinyint(4) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=13 ;

und folgendes bekomme ich zustande:

Code:
CREATE TABLE IF NOT EXISTS `mhpsp_bereiche` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `f1` varchar(50) NOT NULL,
  `f2` char(2) NOT NULL,
  `f3` text NOT NULL,
  `f4` tinyint(4) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM [**] AUTO_INCREMENT=13

Ich bin eigentlich schon ganz zufrieden jedoch wie man an dem [**] erkennen kann fehlt mir da der "DEFAULT CHARSET=latin1" ich könnte den natürlich per Hand eintragen aber das ist ja nicht ganz Sinn der Sache :D

Bisher habe ich alle Informationen mit SHOW TABLE STATUS, SHOW FIELDS und SHOW KEYS herrausfinden können jedoch der charset bleibt mir ein kleines Rätzel.

Wenn ich mir nun von SHOW TABLE STATUS den array ausgeben lasse (Zeile 22) sehe ich dort: [Collation] => latin1_swedish_ci

Kann es sein dass es das ist? Kann/muss ich den splitten (explode) und dann den ersten teil ausgeben?

Soweit zu meinem Problem.
Über Informationen, auf was für Probleme ich mit dem Script stoßen könnte oder was ich vergessen habe wäre ich ebenfalls sehr dankbar.

Ich wünsche frohe Feiertage und danke schonmal im vorraus.
 
Zuletzt bearbeitet:
Eigentlich brauchst du den charset nicht pro Tabelle festlegen. Das wird IMHO von der DB übernommen. Daher genügt es, eine DB im korrekten (gewünschten) Zeichensatz zu erstellen.
 
ah ok. Ich habe es (erstmal) so gemacht wie ich es oben schon geschriben habe.
Funktioniert! Aber wenn ich es nicht brauche lasse ich es einfach weg.
Habe die klasse fast fertig und wollte noch eine downloadfunktion mit reinsetzen.
Also die Klasse erstellt nun das backup in *.sql und nach wunsch wird es noch komprimiert (zip,gz,bz2 wie man will). Obwohl es nun nicht direkt hierzu gehört wollte ich noch fragen wo mein Problem liegt.
folgende Funktion liegt in der Klasse:
PHP:
function download($file){
            global $option;
            $mime = mime_content_type($option['zfolder'].$file);
            header("Content-Type: $mime");
            header("Content-Disposition: attachment; filename=\"$file\"");
            readfile($option['zfolder'].$file);
        }
Variablen sind richtig, das habe ich bereits überprüft. Der Download startet auch wenn ich die funktion aufrufe. Jedoch ist die Datei jedesmal beschädigt. Ich habe eine Idee woran es liegt. Die Googlesuche sagt mir manchmal auf dass es daran liegen kann... aber kein lösung ^^
Meine Vermutung:
Die Klasse liegt in einer seperaten Datei die ich per include_once() einbinde. Jedoch dort wo sie eingebunden wird stehen oben:
PHP:
ob_start();
ini_set('session.use_cookies'     ,1);
ini_set('session.use_only_cookies',1);
ini_set('session.use_trans_sid'   ,0);
session_start ();

und unten:
PHP:
ob_end_flush();

ohne die funktionieren die weiterleitungen per Header bei mir nicht :( aber dadurch sind scheinbar die Dateien "defekt"... Kann es daran liegen? Was kann ich dagegen machen?
 
leider keinen erfolg... Evtl. muss ich dazu eine externe Datei machen die dann den Download aufruft (das sollte gehen)... aber genau dass wollte ich ja vermeiden ...
 
Hast du die Datei mal mit einem stinknormalen Editor geöffnet um zu sehen, ob da überhaupt Binär-Sachen drin stehen? Nicht das da Fehler aus PHP drin sind.
 
oh, gute Idee.
Also Fehler direkt nicht... aber mein Quellcode steht da drin... zumindest bis ca. dem Punkt wo sich die Klasse befindet. Dannach kommt ein Code! Was genau das ist weiß ich nicht... wenn ich es Makiere verändert es sich auch... total komisch ... liegt vielleich am Editor und am Zeichensatz...

Aber das ist ja schon mal ein ansatz... mal sehen ob ich da was finde.
 
Hast du geprüft, was in den Teilen von

readfile($option['zfolder'].$file);

drin steht? Außerdem empfiehlt es sich nach dem readfile() ein exit() oder besser die() zu machen, da sonst noch Rest-Code ausgeführt werden könnte.
 
ok also das die() habe ich hinzugefügt. Ändert ja aber nichts an dem Problem. Trotzdem danke für den Tip.
$option['zfolder'] gibt folgendes aus: ./backups/
$file: usr_web873_3__1303548301.sql.gz

Das passt auch alles Die Dateibezeichnung ist immer: [Datenbankname]__[Zeitstempel].sql.[Kompression(wenn gewünscht)]
Die Datei ist definitiv korrekt und funktionstürchtig. Wenn ich sie herrunterlade(Browserlink) kann ich sie entpacken und habe mein sql datei die ich dann auch direkt im phpMyAdmin wieder importieren kann. Also soweit funktioniert das Script tadellos.

Ich poste nochmal das komplette Script mit Bemerkungen:
PHP:
<?php
    class mysql_backup{
        //Variabeln setzen
        function set_vars($key,$value){
            global $option;
            $option[$key] = $value;
            //Mögliche Variabeln:
            //[host] = Servername (optional-nur für den Kommentar)         
            //[database] = Datenbankname **wichtig**
            //[zfolder] = Ablageordner für die Backupdateien **Wichtig**
            //[zfile] = Dateibezeichnung **Wichtig** (vorne befindet sich immer der Datenbankname. Der rest wird mit dieser Variable festgelegt. Ebenfalls die endung der Unkomprimierten Datei)
            //[datas] = Boolean | entscheidet ob die Datensätze Exportiert werden sollen (optional)
            //[error] = Muss nicht festgelegt werden. Diese Variable speichert Fehlermeldungen.
            //[praefix] = Ändert den Tabellen-Präfix in diesen String (Optional)
            //[comment] = Boolean | legt fest ob kommentare gespeichert werden sollen. (optional)
        }
                      
        //Erstellen des Startkommentars
        function create_db_startcomment(){
            global $option;
            $out = "
-- //////////////////////////////////////////////////////////////\n 
-- Host: ".$option['host']."\n
-- Time: ".date('d.m.Y - H:i')."\n
-- Database: ".$option['database']."\n
-- //////////////////////////////////////////////////////////////\n\n 
            ";
            return $out; 
        }
        
        //Erstellen der Tabellenstruktur aller Tabellen in der Datenbank
        function create_table_structure(){
            global $option;
            
            //Wenn Kommentare geschrieben werden soll...
            if($option['comment']){
                $out = $this-> create_db_startcomment();}                                                              //Kommentar schreiben
            
            //Tabellen auslesen
            $anfrage1=mysql_query("SHOW TABLE STATUS FROM ".$option['database']."");
            while($row1=mysql_fetch_array($anfrage1)){
                //Wenn Kommentar geschrieben werden soll...
                if($option['comment']){
                    $out .= "
-- //////////////////////////////////////////////////////////\n
-- ---- Tabellenstruktur für `".$row1['Name']."`\n
-- //////////////////////////////////////////////////////////\n\n
                    ";                                                                                              //Kommentar schreiben mit Tabellennamen
                }
                    
                $tmp2 = array();                                                                                    //Temparray auf 0 setzen (für mehrere Schleifendurchläufe)
                $out .= "CREATE TABLE ";                                                                            //Schreibe Information
                $out .= "IF NOT EXISTS ";                                                                           //Schreibe Information (Die verhindert das erstellen einer Tabelle wenn diese bereits Existiert)
                
                //Wenn der Präfix geändert werden soll
                if(!empty($option['praefix'])){
                    $ex = explode('_',$row1['Name']);                                                               //Tabellennamen splitte
                    $out.=$option['praefix'];                                                                       //Präfix ersetzen
                    
                    for($i=1;$i<count($ex);$i++){                                                                   //Tabellennamen schreiben
                        $out.="_".$ex[$i];}
                        
                }else{$out.="`".$row1['Name']."` ";}                                                                //Präfix nicht ersetzen. (Original Tabellennamen übernehmen)
                
                $out .= " (\n";                                                                                     //Schreibe Information
                
                //Feldinformationen abfragen                                                                              
                $anfrage2=mysql_query("SHOW FULL FIELDS FROM `".$row1['Name']."`");
                while($row2=mysql_fetch_array($anfrage2)){
                    $tmp .= "`".$row2['Field']."` ".$row2['Type']." ";                                              //Schreibe Information in tmp (Feldname und Type)
                    if($row2['Null']=="NO")               { $tmp .= " NOT NULL";}                                   //Schreibe Information in tmp (NOT NULL)
                    if($row2['Null']=="YES")              { $tmp .= " NULL";}                                       //Schreibe Information in tmp (NULL) 
                    if($row2['Extra']=="auto_increment")  { $tmp .= " AUTO_INCREMENT";}                             //Schreibe Information in tmp (AUTO_INCREMENT) 
                    if($row2['Comment']!="")              { $tmp .= " COMMENT '".$row2['Comment']."'";}             //Schreibe Information in tmp (COMMENT) 
                    $tmp2[] .= $tmp; $tmp="";                                                                       //Übertrage die Daten in den TempArray (mehr informationen unten)
                }
                
                //Keys abfragen
                $anfrage3=mysql_query("SHOW KEYS FROM `".$row1[0]."`");
                while($row3=mysql_fetch_array($anfrage3)){
                    $tmp .= "".$row3['Key_name']." KEY (`".$row3['Column_name']."`)";                               //Schreibe Informationen in tmp (KEY)
                }
                
                if(!empty($tmp)){ $tmp2[] .= $tmp; $tmp="";}                                                        //Wenn der tmp leer ist (Keine KEYS) dann nichs an den TempArray übertragen 
                
                $out .= implode(",\n",$tmp2);                                                                       //Den TempArray in einen String verwandeln und mit einem Komma trennen (so erhält man an der letzten Position kein Komma was sonst zu einem fehler führen würde!)
                $out .= "\n)";                                                                                      //Schreibe Information 
                $out .= " ENGINE=".$row1['Engine']."";                                                              //Schreibe Information (Engine)
                if($row1['Auto_increment']!="")           { $out .= " AUTO_INCREMENT=".$row1['Auto_increment'];}    //Schreibe Information (Auto_increment) 
                $out .= ";";                                                                                        //Schreibe Information (Symikolon zum Abschluss)
                $out .= "\n\n";                                                                                     //Schreibe Information
                if($option['datas']){ $out .= $this->create_table_datas($row1['Name']);}                            //Wenn Datensätze geschrieben werden sollen dann die Funktion aufrufen.
            }
            return $out;        
        }
        //Datensätze exportieren
        function create_table_datas($table){
            global $option;
            //Wenn Kommentare geschrieben werden soll...
            if($option['comment']){
                $out .= "
-- ---- Datensätze für `".$table."`\n                                                           
                ";                                                                                                  //Kommentar schreiben
            }
            $out .= "INSERT INTO `$table` (";                                                                       //Schreibe Informationen
            //Feldinformationen abfragen
            $anfrage1=mysql_query("SHOW FULL FIELDS FROM `".$table."`");                                            
            while($row1=mysql_fetch_array($anfrage1)){
                $tmp[] = "`".$row1['Field']."`";                                                                    //Schreibe Informationen in tmp
            }
            $out .= implode(',',$tmp);$tmp="";                                                                      //Den tmp in einen String verwandeln und mit einem Komma trennen (so erhält man an der letzten Position kein Komma was sonst zu einem fehler führen würde!)
            $out .= ") VALUES\n";                                                                                   //Schreibe Informationen
            
            //Datensätze abrufen
            $anfrage2=mysql_query("SELECT * FROM `".$table."` ");
            if(mysql_num_rows($anfrage2)>0){
                while($row2=mysql_fetch_assoc($anfrage2)){
                    //Den Array absplitten
                    foreach($row2 as $value){                                                                       
                        if(!is_int($value)){$value="'".addslashes($value)."'";}                                     //Wenn es sich um keinen Integer handelt den Wert in '' setzen und interne ' mit einem \ versehen (addslashes)
                        $tmp[] = $value;                                                                            //Schreibe Informationen in tmp
                    }
                    $tmp2[] = "(".implode(",",$tmp).")";                                                            //Den tmp in einen String verwandeln, mit einem Komma trennen (so erhält man an der letzten Position kein Komma was sonst zu einem fehler führen würde!) und in den TempArray speichern
                    $tmp=array();                                                                                   //tmp zurücksetzen
                }
                $out .= implode(",\n",$tmp2).";";                                                                   //Den TempArray in einen String verwandeln und mit einem Komma trennen (so erhält man an der letzten Position kein Komma was sonst zu einem fehler führen würde!). Ebenfalls Jeden Datensatz mit einem Zeilenumbruch versehen um die Übersicht zu behalten
            }else{
                $out="";                                                                                            //Wenn keine Datensätze vorhanden sind die Informationsvariable auf empty setzen!
            } 
            return $out;                                                                                            //Informationen übergeben
        }
        
        //SQL Datei abspeichern
        function save_backup($value){
            global $option;
            //Testen ob der Festgelegte Ordner schreibrechte für PHP besitzt
            if(is_writable($option['zfolder'])){
                $file = fopen($option['zfolder'].$option['database'].$option['zfile']."",'w+');                     //die SQL Datei öffnen bzw. erstellen wenn nicht vorhanden
                if(!$file){
                    $option['error'][]="Konnte in Ordner nicht schreiben";                                          //Wenn es nicht möglich ist die Datei zu öffnen oder zu erstellen eine Fehlermeldung festlegen
                }
                fwrite($file,$value);                                                                               //Informationen in die Datei schreiben
                fclose($file);                                                                                      //Datei schließen
                return "".$option['database']."".$option['zfile']."";                                               //Dateinamen übergeben
            }else{
                $option['error'][]="Backupordner nicht beschreibbar.";                                              //Fehlermeldung generrieren wenn kein schreibrecht vorhanden ist.
            }
        }
        
        //SQL Datei komprimieren
        function compress_file($type,$str,$file){
            global $option;
            switch($type){
                case 'zip':
                    if(class_exists("ZipArchive")){
                        $zip = new ZipArchive;
                        $zip->open($option['zfolder'].$option['database'].$option['zfile'].".zip");
                        $zip->addFile($file);
                        $zip->close();   
                        unlink($option['zfolder'].$file);
                        return $file.".zip";
                    }else{
                        $option['error'][]="Die Funktion \"ZipArchive\" ist leider nicht verfügbar";
                    }
                    break;
                case 'gz':
                    if(function_exists("gzopen")){
                        $gz = gzopen($option['zfolder'].$option['database'].$option['zfile'].".gz","wb");
                        gzwrite($gz, $str);
                        gzclose($gz);
                        unlink($option['zfolder'].$file);
                        return $file.".gz";
                    }else{
                        $option['error'][]="Die Funktion \"ZLib\" ist leider nicht verfügbar";
                    }
                    break;
                case 'bz2':
                    if(function_exists("bzopen")){
                        $bz = gzopen($option['zfolder'].$option['database'].$option['zfile'].".bz2","wb");
                        gzwrite($bz, $str);
                        gzclose($bz);
                        unlink($option['zfolder'].$file);
                        return $file.".bz2";
                    }else{
                        $option['error'][]="Die Funktion \"BZip2\" ist leider nicht verfügbar";
                    }
                    break;
                case 'none':
                    return $option['database'].$option['zfile'].".sql";
                    break;
                default:
                    $option['error'][]="Dieses Kompressionsformat ist leider nicht möglich.";
                    break;
            }    
        }
        function download($file){
            global $option;
            $mime = mime_content_type($option['zfolder'].$file);
            /*
            echo $mime."<br>";
            echo filesize($option['zfolder'].$file)."<br>";
            echo $option['zfolder'].$file;
            */
            ///*
            header("Content-Type: $mime");
            header("Content-Length:".filesize($option['zfolder'].$file));
            header("Content-Disposition: attachment; filename=\"$file\"");
            
            readfile($option['zfolder'].$file);
            exit;
            //*/
        }
    }
?>
Ganz unten befindet sich die funktion download().
Die letzten 3 Funktionen sind wichtig dafür.
save_backup() Speichert die sql datei und übergibt den namen der SQL Datei
compress_file() Komprimiert die Datei und übergibt den namen der Komprimierten Datei
download() soll dann den Download starten.

Wenn ich das mit der Namensüberhabe nicht mache habe ich zwischenzeitlich das Problem dass die zu Packende Datei auf einmal anders heißt weil schon Zeit verstrichen ist... daher habe ich das so gelöst.

Also man muss nicht alles durcharbeiten aber evtl. liegt der fehler ja irgendwo anders...

Und so sieht es dann aus wenn ich die klasse/funktionen aufrufe:

PHP:
            //Zeit stoppen [Start]
            $time_start = microtime_float();
            
            //Klasse Starten
            $backup = new mysql_backup;
            
            //Wichtige Variablen festlegen
            $backup -> set_vars('host',$server);                  //Servername (nur für den Kommentar)
            $backup -> set_vars('database',$datenbank);           //Datenbankname **WICHTIG**
            $backup -> set_vars('zfolder','./backups/');          //Ablageort für die Dateien **WICHTIG**
            $backup -> set_vars('zfile',"__".time().".sql");      //Standard Dateiname (Datenbankname wird IMMER davor gesetzt) **WICHTIG**
            $backup -> set_vars('datas',true);                    //BOOL für das erstellen der Datensätze. True= Exportiert auch die passenden Datensätze ; False= Exportiert nur die Tabellenstruktur
            $backup -> set_vars('error','');                      //Container für Fehlermeldungen.
            
            //Präfix ändern...                
            if($_POST['praefix']=="noedit"){
                $backup -> set_vars('praefix','');}else{          //nein keine änderung (empty)
                $backup -> set_vars('praefix',"__PRAEFIX__");}    //Präfix ändern in __PRAEFIX__
            
            //Kommentare schreiben...  
            if($_POST['comment']=="set"){
                $backup -> set_vars('comment',true);}else{        //True= schreiben
                $backup -> set_vars('comment',false);}            //False= nicht schreiben (speicherplatz sparen)
                
            //String füllen mit den Daten
            $backup_str = $backup -> create_table_structure();
            //Die Daten in eine SQL-Datei schreiben und abspeichern (Am festgelegten Ort)
            //Der Rückgabewert ist der Dateiname (sql-datei)
            $backfile = $backup -> save_backup($backup_str);
            
            //SQL-Datei in ein Archiv legen (Komprimieren)
            //Rückgabewert ist der Dateiname der Komprimierten Datei.
            $compressfile = $backup -> compress_file($_POST['save_as'],$backup_str,$backfile);
            
            //ggf. die Datei direkt zum Download auswählen
            if($_POST['save_location']=="download"){
                $backup -> download($compressfile);}
            
            //Zeit stoppen [ENDE]
            $time_end = microtime_float();
            $time = $time_end - $time_start;
            echo "<center>Backup erstellt in ".number_format($time, 2)." Sekunden</center>";
 
Kannst du mal ein Export von Dummy-Daten anhängen? Wenn das nicht geht, kannst du es ja per PN senden, ich bezeichne mich mal als vertrauenswürdig :-)

Ich hab den Thread noch mal gelesen: Warum musst du unbedingt ob_start() machen? Ich meine, sind da etwa Ausgaben, bevor die Headers gesendet werden? Wenn ja, welche Ausgaben sind das? Zufälligerweise die Byte-order-Mark (BOM)?
 
Zurück