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);
}

// pouzivano na nekolika mistech v zaloze
// zapis do souboru
// @param string $file_name
// @param string $content
// @return null abych si automaticky vynulloval promennou
public 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);
	
	// zaloha po jedne 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);
			
			// ukladame 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 zapis do souboru a vynulovani promenne. abych neshazoval server
				if(strlen($_output) > $limit){
					$_output = $this->writeToFile($file_name, $_output);
					++$this->backupInfo['written'];
				}
			}
			
			unset($_select);
			
			// pokud tu jsou nejake resty. zbavime se jich. tady uz tech 5 mega fakt testovat nebudu a rovnou to zapisu
			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.

// Edit 4. 5. 2020: Text se týká PHP5. Pod PHP7 už třída fungovat nebude, protože všechny mysql_ funkce skončí chybou.