Tworzenie kopii bazy danych z poziomu PHP czyli jak szybko i skutecznie zrobić backup bazy danych

Kategoria: Javascript, HTML i CSS



codecalm
23 sierpień 2009 - 13:32
Wiele z nas nie zdaje sobie sprawy z bezpieczeństwa naszej strony internetowej. Włamanie do serwisu może skończyć się w najlepszym przypadku skompromitowaniem, nieraz utratą całych danych. Podobnie jest w przypadku uszkodzeń serwera, czy poprostu przypadkowym usunięciem bazy danych. Nieraz nasz kilkuletni dorobek ginie w kilka sekund. Dobrym przykładem jest tu serwis społeczniściowy ma.gnolia.com. Cała baza danych została zniszczona w wyniku uszkodzenia serwera. Niestety serwis zakończył swoja działalność.

Aby jednak nas nie spotkała taka "niespodzianka" powinniśmy regularnie tworzyć kopie zapasowe - nie tylko plików, ale i bazy danych. Najwygodniejszym rozwiązaniem jest chyba użycie wbudowanej w PHPMyAdmin'a funkcji "Eksportuj". Jednak każdorazowe wchodzenie, logowanie i eksportowanie jest trochę nużące i z czasem kłopotliwe. Możemy jednak zautomatyzowac ten proces, używając np komendy system() z odpowiednimi parametrami. Lecz nie jest to najlepsze rozwiązanie, gdyż większość hostingów blokuje tą funkcję ze względów bezpieczeństwa. Co więc nam pozostało? Skorzystanie z gotowych rozwiązań, albo napisanie własnego skryptu.

Pokażę dzisiaj w jaki sposób stworzyć prostą klasę do generowania zrzutu bazy danych. Klasa będzie oparta na PHP5, jednak kilka drobnych zmiań spowoduje, że będzie działała także pod starszą wersją. No to zaczynamy:

Tworzenie skryptu

<?php class DbDump { public $drop = TRUE; private $db; private $sql; private $fields = array(); public function __construct($host = FALSE, $login = FALSE, $pass = FALSE, $db = FALSE) { @mysql_connect($host, $login, $pass) or die('Blad przy polaczeniu z baza danych'); @mysql_select_db($db) or die('Blad przy wyoborze bazy danych'); mysql_query('SET NAMES utf8'); $this->db = $db; }
Nasz skrypt zaczynamy od stworzenia kilku zmiennych, które będą używane w naszej klasie. $drop będzie odpowiedzialna za przechowywanie informacji, czy do zrzutu ma być dodawane polecenie "DROP TABLE IF EXISTS". Jest to zmienna publiczna, gdyż musimy mieć na nią wpływ podczas ustawiania konfiguracji naszej kopii. Ponadto dodaliśmy jeszcze zmienne $db, $sql oraz $fields. Ich zastosowanie opiszę w dalszej części. Kolejnym elementem naszej klasy będzie stworzenie konstruktora, który będzie nam inicjował połączenie z bazą danych. Po udanym połączeniu i wybraniu bazy ustawiamy kodowanie znaków na utf-8, aby nie było problemów z polskimi znakami. Ponadto zmienna $this->db będzie przetrzymywała nam nazwę bazy danych na której operujemy.

private function get_tables() { $query = mysql_query('SHOW TABLES'); $tables = array(); while($row = mysql_fetch_row($query)) { $tables[] = $row[0]; } return $tables; }
Pierwszą metodą, którą stworzymy będzie get_tables(). Pobierze ona informacje o wszystkich tabelach w bazie za pomocą polecenia SQL ?SHOW TABLES?. Jako że nazwy tabel traktowane są jako wiersze (polecam użycie tej konstrukcji np w PHPMyAdminie) za pomocą pętli while() przerzucamy je do tablicy i zwracamy. W ten sposób będziemy mogli operować na danych funkcją foreach().

private function show_create_table($table) { $query = mysql_query("SHOW CREATE TABLE `{$table}`"); $row = mysql_fetch_row($query); return $row[1]; }
Przy tworzeniu zrzutu bazy danych musimy pamiętać o stworzeniu kopii dwóch rzeczy: struktury i danych. O ile dane jesteśmy w stanie wyciągnąć chociażby zapytaniem ?SELECT * FROM tabela?, to ze strukturą jest już gorzej. Jednak z pomocą przychodzi nam polecenie ?SHOW CREATE TABLE tabela?, które zwraca nam kod zapytania tworzącego strukturę danej tabeli. Wynik zwracamy funkcją return().

private function fields_info($table) { $query = mysql_query("SELECT * FROM `{$table}`"); $this->fields = array(); while($field = mysql_fetch_field($query)) { $this->fields[$field->name] = in_array(strtolower($field->type), array('tinyint', 'smallint', 'mediumint', 'int', 'bigint')) ? TRUE : FALSE; } return TRUE; }
Przy tworzeniu kopii potrzebujemy informację, czy dana kolumna przechowuje wartość tekstową, czy liczbową, aby móc ja potem odpowiednio formatować. Jako że nazwy kolumn są unikatowe, możemy przechowywać je jako indeksy w tablicy. Jako wartość będzie przetrzymywana informacja, czy dana kolumna jest typu liczbowego, czy też nie. W tym celu za pomocą funkcji mysql_fetch_field() pobieramy informacje o kolumnach. Wykonujemy prosty test na typ danych w kolumnie i umieszczamy wynik w tablicy.

private function field_str() { $tmp = array(); foreach($this->fields as $field=>$val) { $tmp[] = '`'.$field.'`'; } return implode(', ', $tmp); }
Aby kopia była zgodna ze standardami dane będziemy zrzucać w postaci "INSERT INTO tabela (kolumny) VALUES (wartości)". Funkcja field_str() będzie odpowiedzialna za utworzenie listy kolumn, które występują w tabeli. W tym celu dane umieszczone w tablicy $this->fields (utworzone w poprzedniej funkcji) kopiujemy do tymczasowej tablicy, a następnie łączymy za pomocą funkcji implode().

private function get_rows($table) { $query = mysql_query("SELECT * FROM {$table} LIMIT 2"); $rows = array(); while($row = mysql_fetch_assoc($query)) { $rows[] = $row; } return $rows; }
Czym by była kopia bez danych. Za pomocą powyższej funkcji pobieramy wszystkie rekordy z bazy i zwracamy jako tablicę. W ten sposób mamy możliwość operowania na danych pętlą foreach().

private function row_str($rows) { $tmp = array(); foreach($rows as $row=>$val) { if($val === NULL) { $tmp[] = 'NULL'; } elseif($this->fields[$row] == TRUE) { $tmp[] = $val; } else { $tmp[] = '"'.mysql_real_escape_string($val).'"'; } } return implode(', ', $tmp); }
Teraz najważniejszy element naszego skryptu, czyli przygotowywanie wartości przechowywanych w kolumnach. W tym celu posługujemy się danymi, które przechowuje tablica $this->fields. W zależności od typu zwracamy warość NULL gdy kolumna jest pusta (zwróćmy uwagę na użycie operatora ===), zwykłą wartość gdy mamy do czynienia z liczbą lub odpowiednio zabezpieczony i ujęty w cudzysłowia ciąg znaków. Na koniec znowy składamy wszystko w jeden ciąg i zwracamy.

public function create() { $this->sql = "#\n# Zrzut bazy danych `{$this->db}`\n#\n\n"; foreach($this->get_tables() as $table) { $this->sql .= "#\n# Struktura tabeli `{$table}`\n#\n\n"; if($this->drop == TRUE) { $this->sql .= "DROP TABLE IF EXISTS `{$table}`;\n"; } $this->sql .= $this->show_create_table($table).";\n\n"; $this->sql .= "#\n# Zrzut danych tabeli `{$table}`\n#\n\n"; $this->fields_info($table); $field_str = $this->field_str(); foreach($this->get_rows($table) as $row) { $row_str = $this->row_str($row); $this->sql .= "INSERT INTO `{$table}` ({$field_str}) VALUES ({$row_str});\n"; } $this->sql .= "\n\n"; } return $this->sql; }
I teraz najważniejsza część, czyli złozenie wszsytkiego w jedną całość. Tworzymy więc funkcję create(), która będzie zwracała gotowy kod przechowujący zrzut bazy danych.

Na początku do zapytania dodajemy w komentarzach informacje o nazwie bazy danych. Następnie przeglądamy wszystkie tebele pętlą foreach() i do każdej tabeli również dodajemy odpowiednie komentarze.

W zależności od tego czy użytkownik chce dodawać polecenie DROP dodajemy zapytanie lub nie. W kolejnym kroku dołączamy informacje o strukturze tabeli i odpowiedni komentarz. Na końcu przeglądamy wszystkie rekordy i dodajemy ich zawartość do zrzutu oraz zwracamy całe zapytanie.

Podczas tworzenia kopii używamy znaków końca linii (\n), aby skrypt był czytelny i zrozumiały.

} ?>
Nie zapomnijmy zamknąć nawiasu kwadratowego otwartego przy tworzeniu nowej klasy.
Tworzenie kopii w praktyce

Mając gotowy kod możemy go bez problemu przetestować. Wygląda to w taki sposób:
$dump = new DbDump('localhost', 'login', 'haslo', 'baza_danych'); echo '<pre>'; echo htmlspecialchars($dump->create()); echo '</pre>';

Funkcji htmlspecialchars() użyłem w celu poprawnego wyświetlania tagów HTML przechowywanych w bazie.

Oczywiście nic nie stoi na przeszkodzie, aby zrzut zapisywać do odpowiedniego pliku na serwerze. Jeżeli dodatkowo podepniemy skrypt pod CRON?a serwer sam będzie pamiętał za nas o stworzeniu kopii bezpieczeństwa.

Podsumowanie

Jak widać odpowiedni kod może uratować naszą witrynę przez zagładą. Pamiętajmy, aby kopie bazy nie były przetrzymywane na serwerze, a już napewno w miejscu dostępnym dla gości witryny. W bazie przechowywane są nieraz tajne dane, które nie powinny być ogólnodostępne.

Mam nadzieję, że skrypt ten pozwoli uratować niejeden ciekawy serwis.

Na koniec umieszczam cały kod źródłowy skryptu.


Podobne tematy:
Jak szybko i skutecznie przytyć??
jak szybko to zrobić cd..
tworzenie mysql\'owej procedury z poziomu php
Czytanie XML z poziomu PHP. Jak najlepiej ?
Jak szybko i skutecznie schudnąć ?
Jak szybko i skutecznie przytyć??