Návrh databázové třídy - díl IV: Export databáze

Návrh databázové třídy - díl IV: Export databáze

Dnešní pokračování pohádky o databázové třídě si vezme pod lupu pár konkrétních metod. Půjde o funkce na export celé databáze s možností force download nebo postupného ukládání do souboru. O možnostech exportu databáze moc článků napsáno nebylo, navíc se nejedná o úplně triviální algoritmus s pár vnořenými cykly. V následujícím článku vám předvedu, jak problém vyřešit s elegancí.

Kostra řešení bude poměrně jednoduchá:

SHOW TABLES

foreach ($tables) {
	SHOW CREATE TABLE
	SELECT * FROM $table
	while (FETCH_ROW) {
		foreach ($row) {
			mysql_real_escape_string($row);					
		}
		$export .= "INSERT INTO $table VALUES implode(, $row)"
	}
}

Nejdříve zjistíme, jaké tabulky exportujeme, následně od každé z nich získáme strukturu přes SHOW CREATE TABLE, vybereme hvězdičku, provedeme escapování a uložíme znění SQL dotazu na insert.

Metody na export databáze budou 2. Budou si podobné, protože v rámci zachování čitelnosti kódu není dobry nápad cpát do už tak velké funkce hromadu podmínek. První metoda uloží celý export do proměnné a nabídne soubor k uložení, druhá bude data postupně ukládat do souboru, který si pak můžeme stáhnout přes FTP klienta.

Před uvedením výsledné funkce si ale nejprve ukážeme pár podpůrných metod, jenž se nám budou hodit. O prováděném exportu chceme samozřejmě mít nějaké zběžné informace, které nám poskytnou následující funkce.

private function prepareBackupInfo() {
	$this->backupInfo['date_pattern'] =  @date('Y-m-d_H.i.s');
	$this->backupInfo['timestamp'] = strtr($this->backupInfo['date_pattern'], array('_' => ' ', '.' => ':'));
	$this->backupInfo['start_time'] = $this->startTime();
	$this->backupInfo['queries'] = 0;
	$this->backupInfo['written'] = 0;
}

private function appendBackupInfo() {
	$output = "";
	$output .= "\n";
	$output .= "--\n";
	$output .= "-- Queries contained: ".$this->backupInfo['queries']."\n";
	$output .= "-- Shots to file: ".$this->backupInfo['written']."\n";
	$output .= "-- Total time: ".$this->stopTime($this->backupInfo['start_time'])."\n";
	$output .= "-- Timestamp start: ".$this->backupInfo['timestamp']."\n";
	$output .= "--\n";
	return $output;
}

Metoda DB::downloadHeaders() zašle potřebné hlavičky pro force download, naproti tomu Db::writeToFile() bude obsluhovat průběžný zápis do souboru. Nakonec uvedu ještě funkci Db::addExportCondition(), kterou využijeme v případě, když budeme chtít exportovat pouze některé tabulky nebo jejich části.

private function downloadHeaders($file_size, $file_type, $file_name) {
	header("Pragma: public");
	header("Expires: 0");
	header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
	header("Cache-Control: private", false);
	header("Content-Transfer-Encoding: binary");
	header("Content-Type: ".$file_type);
	header("Content-Length: ".$file_size);
	header("Content-Disposition: attachment; filename=".$file_name);
}

/**
 * používáno na několika místech v záloze
 * zápis do souboru
 * @param string $file_name
 * @param string $content
 * @return null abych si automaticky vynulloval proměnnou
 **/
private function writeToFile($file_name, $content) {
	$file = fopen($file_name, 'a');
	fwrite($file, $content);
	fclose($file);
	return null;
}

public function addExportCondition($table, $where = null) {
	$this->exportConditions[$table] = $where; 
}

Nyní už se můžeme pustit do exportu. Funkci uvedu pouze jednu, metodu Db::dumpAndSave() - tu složitější. Tělo pouze kopíruje úvodní vzor, přidaná je podmínka na kontrolu nastavení exportu (konkrétní tabulky a vnitřní podmínky).

public function dumpAndSave() {
	$limit = 5000000;
		
	$this->prepareBackupInfo();
	$file_name = $this->backupFolder.$this->database.'_'.$this->backupInfo['date_pattern'].'.sql';
	
	if ($this->backupFolder && !is_dir($this->backupFolder)) {
		mkdir($this->backupFolder, '0777');
	}
	
	$_tables = array();
	$_select = $this->query('SHOW TABLES');
	
	while ($t = $_select->fetch(FETCH_ROW)) {
		$_tables[] = $t[0];
	}
	
	unset($_select);

	$_output = null;
	$_output = "--\n-- Database: `".$this->database."`\n--\n\n-- --------------------------------------------------------\n\n";
	$_output = $this->writeToFile($file_name, $_output);
	
	// záloha po jedné tabulce
	foreach ($_tables as $value) {
		if (!count($this->exportConditions) || (count($this->exportConditions) && isset($this->exportConditions[$value]))) {
		
			// show create table
			$_select_ct = $this->query("SHOW CREATE TABLE `{$value}`");
			$_row = $_select_ct->fetch(FETCH_ROW);
			
			// ukládame do souboru show create table
			$_output = null;
			$_output .= "--\n";
			$_output .= "-- `".$_row[0]."`\n";
			$_output .= "--\n\n";
			$_output .= $_row[1]." ;\n\n";
			$_output = $this->writeToFile($file_name, $_output);
			
			unset($_select_ct);
			
			++$this->backupInfo['queries'];

			if (isset($this->exportConditions[$value])) {
				$_select = $this->query("SELECT * FROM `{$value}` {$this->exportConditions[$value]}");
			} else {
				$_select = $this->query("SELECT * FROM `{$value}`");
			}
			
			while ($t = $_select->fetch(FETCH_ROW)) {
				foreach ($t as $k => $i) {
					$t[$k] = "'".$this->escape($i)."'";
				}
				
				$_output .= "INSERT INTO `{$value}` VALUES (".implode(',', $t).");\n";
				++$this->backupInfo['queries'];
				
				// co 5 mega, to zápis do souboru a vynulování proměnné. abych neshazoval server
				if (strlen($_output) > $limit) {
					$_output = $this->writeToFile($file_name, $_output);
					++$this->backupInfo['written'];
				}
			}
			
			unset($_select);
			
			// pokud tu jsou nějaké resty. zbavime se jich. tady už těch 5 mega fakt testovat nebudu a rovnou to zapíšu
			if ($_output) {
				$_output = $this->writeToFile($file_name, $_output);
				++$this->backupInfo['written'];
			}
		}
	}
	
	$_output .= $this->appendBackupInfo();
	$_output = $this->writeToFile($file_name, $_output);
}

Aplikace metod bude opět naprosto triviální. Zakomentované řádky nastaví, že se exportuje celá tabulka s kategoriemi a tabulka s produkty zařazenými do kategorie 1, 2 nebo 3.

$db = new Db;
// $db->addExportCondition('shop_categories', '');
// $db->addExportCondition('shop_products', 'WHERE category_id IN (1,2,3)');
$db->dumpAndSave();

Znění druhé funkce si většina z vás jistě domyslí. Ostatní si budou muset počkat na zveřejnění zdrojového kódu, které je naplánováno na šestý díl. Příště si povíme něco o logování změn.

Vytisknout článek


Komentáře k článku "Návrh databázové třídy - díl IV: Export databáze"

Gravatar
Filip

1/4 Pátek 12. Listopadu 2010, 19:59  |  Google Chrome, Windows XP

Ahoj
tvé články se mi moc líbí a často se zde inspiruji při psaní kódu, ale je škoda, že články nevydáváš nijak pravidelně (třeba 1 x měsíčně). Já a jistě i spousta tvých čtenářů, se těšíme na pokračování tohoto článku.

S pozdravem Filda

Na komentář reagovali: @Mike ↓ Reaguj ↓

2/4 Sobota 13. Listopadu 2010, 18:27  |  Opera 10.63, Windows XP

@Filip: Ahoj, v prvé řadě díky tvůj zájem. Bohužel mohu říct jediné, a to, že články píšu pouze když na ně mám čas. A posledních pár měsíců čas vůbec nebyl... Žádnou pravidelnost slíbit nemohu, ale udělám vše pro to, aby to napravil :-)

Reaguj ↓

Gravatar
Vladimir Kasik

3/4 Pátek 7. Ledna 2011, 15:11  |  Firefox 3.6.13, Windows Vista

Fascinuji me talentovani lide,a ty jsi jednoznacne jeden z nich.

Na komentář reagovali: @Mike ↓ Reaguj ↓

4/4 Pondělí 10. Ledna 2011, 23:38  |  Opera 11.00, Windows XP

@Vladimir Kasik: Díky ;-)

Reaguj ↓

Přidat komentář







Nevím, kolik to je
Erset la tari eimmu, Ina ramanisu melammu, Baru dinau, Allatu Nergal, Sar kissati

Chcete-li mne kontaktovat, napište vzkaz do návštěvní knihy, uveďte Váš e-mail a já Vám na něj obratem odpovím. PS.: Jestli jste pro, můžeme si tykat.