├── .github └── funding.yml ├── composer.json ├── examples ├── importFromFile.php ├── sendAsFile.php └── writeToFile.php ├── license.md ├── readme.md └── src ├── MySQLDump.php └── MySQLImport.php /.github/funding.yml: -------------------------------------------------------------------------------- 1 | github: dg 2 | custom: "https://nette.org/make-donation?to=mysql-dump" 3 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dg/mysql-dump", 3 | "description": "MySQL database dump.", 4 | "keywords": ["mysql"], 5 | "homepage": "https://github.com/dg/MySQL-dump", 6 | "license": ["BSD-3-Clause"], 7 | "authors": [ 8 | { 9 | "name": "David Grudl", 10 | "homepage": "http://davidgrudl.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=7.1" 15 | }, 16 | "autoload": { 17 | "classmap": ["src/"] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/importFromFile.php: -------------------------------------------------------------------------------- 1 | onProgress = function ($count, $percent) { 15 | if ($percent !== null) { 16 | echo (int) $percent . " %\r"; 17 | } elseif ($count % 10 === 0) { 18 | echo '.'; 19 | } 20 | }; 21 | 22 | $import->load('dump.sql.gz'); 23 | 24 | $time += microtime(true); 25 | echo "FINISHED (in $time s)"; 26 | -------------------------------------------------------------------------------- /examples/sendAsFile.php: -------------------------------------------------------------------------------- 1 | write(); 20 | -------------------------------------------------------------------------------- /examples/writeToFile.php: -------------------------------------------------------------------------------- 1 | save('dump ' . date('Y-m-d H-i') . '.sql.gz'); 14 | 15 | $time += microtime(true); 16 | echo "FINISHED (in $time s)"; 17 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008, Copyright (c) 2008 David Grudl 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of David Grudl nor the names of its 15 | contributors may be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | MySQL Dump Utility 2 | ================== 3 | 4 | This is a backup utility used to dump a database for backup or transfer to another MySQL server. 5 | The dump typically contains SQL statements to create the table, populate it, or both. 6 | 7 | It requires PHP 5.6 (release 1.5) or PHP 7.1 or later. 8 | 9 | Usage 10 | ----- 11 | 12 | Create [MySQLi](http://www.php.net/manual/en/mysqli.construct.php) object and pass it to the MySQLDump: 13 | 14 | ```php 15 | $db = new mysqli('localhost', 'root', 'password', 'database'); 16 | $dump = new MySQLDump($db); 17 | ``` 18 | 19 | You can optionally specify how each table or view should be exported: 20 | 21 | ```php 22 | $dump->tables['search_cache'] = MySQLDump::DROP | MySQLDump::CREATE; 23 | $dump->tables['log'] = MySQLDump::NONE; 24 | ``` 25 | 26 | Then simply call `save()` or `write()`: 27 | 28 | ```php 29 | $dump->save('export.sql.gz'); 30 | ``` 31 | 32 | Import dump from file to database this way: 33 | 34 | ```php 35 | $import = new MySQLImport($db); 36 | $import->load('dump.sql.gz'); 37 | ``` 38 | 39 | If you like it, **[please make a donation now](https://nette.org/make-donation?to=mysql-dump)**. Thank you! 40 | -------------------------------------------------------------------------------- /src/MySQLDump.php: -------------------------------------------------------------------------------- 1 | self::ALL, 26 | ]; 27 | 28 | /** @var mysqli */ 29 | private $connection; 30 | 31 | 32 | /** 33 | * Connects to database. 34 | */ 35 | public function __construct(mysqli $connection, string $charset = 'utf8mb4') 36 | { 37 | $this->connection = $connection; 38 | 39 | if ($connection->connect_errno) { 40 | throw new Exception($connection->connect_error); 41 | 42 | } elseif (!$connection->set_charset($charset)) { // was added in MySQL 5.0.7 and PHP 5.0.5, fixed in PHP 5.1.5) 43 | throw new Exception($connection->error); 44 | } 45 | } 46 | 47 | 48 | /** 49 | * Saves dump to the file. 50 | */ 51 | public function save(string $file): void 52 | { 53 | $handle = strcasecmp(substr($file, -3), '.gz') ? fopen($file, 'wb') : gzopen($file, 'wb'); 54 | if (!$handle) { 55 | throw new Exception("ERROR: Cannot write file '$file'."); 56 | } 57 | $this->write($handle); 58 | } 59 | 60 | 61 | /** 62 | * Writes dump to logical file. 63 | * @param resource 64 | */ 65 | public function write($handle = null): void 66 | { 67 | if ($handle === null) { 68 | $handle = fopen('php://output', 'wb'); 69 | } elseif (!is_resource($handle) || get_resource_type($handle) !== 'stream') { 70 | throw new Exception('Argument must be stream resource.'); 71 | } 72 | 73 | $tables = $views = []; 74 | 75 | $res = $this->connection->query('SHOW FULL TABLES'); 76 | while ($row = $res->fetch_row()) { 77 | if ($row[1] === 'VIEW') { 78 | $views[] = $row[0]; 79 | } else { 80 | $tables[] = $row[0]; 81 | } 82 | } 83 | $res->close(); 84 | 85 | $tables = array_merge($tables, $views); // views must be last 86 | 87 | $this->connection->query('LOCK TABLES `' . implode('` READ, `', $tables) . '` READ'); 88 | 89 | $db = $this->connection->query('SELECT DATABASE()')->fetch_row(); 90 | fwrite($handle, '-- Created at ' . date('j.n.Y G:i') . " using David Grudl MySQL Dump Utility\n" 91 | . (isset($_SERVER['HTTP_HOST']) ? "-- Host: $_SERVER[HTTP_HOST]\n" : '') 92 | . '-- MySQL Server: ' . $this->connection->server_info . "\n" 93 | . '-- Database: ' . $db[0] . "\n" 94 | . "\n" 95 | . "SET NAMES utf8mb4;\n" 96 | . "SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO';\n" 97 | . "SET FOREIGN_KEY_CHECKS=0;\n" 98 | . "SET UNIQUE_CHECKS=0;\n" 99 | . "SET AUTOCOMMIT=0;\n" 100 | ); 101 | 102 | foreach ($tables as $table) { 103 | $this->dumpTable($handle, $table); 104 | } 105 | 106 | fwrite($handle, "COMMIT;\n"); 107 | fwrite($handle, "-- THE END\n"); 108 | 109 | $this->connection->query('UNLOCK TABLES'); 110 | } 111 | 112 | 113 | /** 114 | * Dumps table to logical file. 115 | * @param resource 116 | */ 117 | public function dumpTable($handle, $table): void 118 | { 119 | $mode = isset($this->tables[$table]) ? $this->tables[$table] : $this->tables['*']; 120 | if ($mode === self::NONE) { 121 | return; 122 | } 123 | 124 | $delTable = $this->delimite($table); 125 | $res = $this->connection->query("SHOW CREATE TABLE $delTable"); 126 | $row = $res->fetch_assoc(); 127 | $res->close(); 128 | 129 | fwrite($handle, "-- --------------------------------------------------------\n\n"); 130 | 131 | $view = isset($row['Create View']); 132 | 133 | if ($mode & self::DROP) { 134 | fwrite($handle, 'DROP ' . ($view ? 'VIEW' : 'TABLE') . " IF EXISTS $delTable;\n\n"); 135 | } 136 | 137 | if ($mode & self::CREATE) { 138 | fwrite($handle, $row[$view ? 'Create View' : 'Create Table'] . ";\n\n"); 139 | } 140 | 141 | if (!$view && ($mode & self::DATA)) { 142 | fwrite($handle, 'ALTER ' . ($view ? 'VIEW' : 'TABLE') . ' ' . $delTable . " DISABLE KEYS;\n\n"); 143 | $numeric = []; 144 | $res = $this->connection->query("SHOW COLUMNS FROM $delTable"); 145 | $cols = []; 146 | while ($row = $res->fetch_assoc()) { 147 | $col = $row['Field']; 148 | $cols[] = $this->delimite($col); 149 | $numeric[$col] = (bool) preg_match('#^[^(]*(BYTE|COUNTER|SERIAL|INT|LONG$|CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER)#i', $row['Type']); 150 | } 151 | $cols = '(' . implode(', ', $cols) . ')'; 152 | $res->close(); 153 | 154 | 155 | $size = 0; 156 | $res = $this->connection->query("SELECT * FROM $delTable", MYSQLI_USE_RESULT); 157 | while ($row = $res->fetch_assoc()) { 158 | $s = '('; 159 | foreach ($row as $key => $value) { 160 | if ($value === null) { 161 | $s .= "NULL,\t"; 162 | } elseif ($numeric[$key]) { 163 | $s .= $value . ",\t"; 164 | } else { 165 | $s .= "'" . $this->connection->real_escape_string($value) . "',\t"; 166 | } 167 | } 168 | 169 | if ($size == 0) { 170 | $s = "INSERT INTO $delTable $cols VALUES\n$s"; 171 | } else { 172 | $s = ",\n$s"; 173 | } 174 | 175 | $len = strlen($s) - 1; 176 | $s[$len - 1] = ')'; 177 | fwrite($handle, $s, $len); 178 | 179 | $size += $len; 180 | if ($size > self::MAX_SQL_SIZE) { 181 | fwrite($handle, ";\n"); 182 | $size = 0; 183 | } 184 | } 185 | 186 | $res->close(); 187 | if ($size) { 188 | fwrite($handle, ";\n"); 189 | } 190 | fwrite($handle, 'ALTER ' . ($view ? 'VIEW' : 'TABLE') . ' ' . $delTable . " ENABLE KEYS;\n\n"); 191 | fwrite($handle, "\n"); 192 | } 193 | 194 | if ($mode & self::TRIGGERS) { 195 | $res = $this->connection->query("SHOW TRIGGERS LIKE '" . $this->connection->real_escape_string($table) . "'"); 196 | if ($res->num_rows) { 197 | fwrite($handle, "DELIMITER ;;\n\n"); 198 | while ($row = $res->fetch_assoc()) { 199 | fwrite($handle, "CREATE TRIGGER {$this->delimite($row['Trigger'])} $row[Timing] $row[Event] ON $delTable FOR EACH ROW\n$row[Statement];;\n\n"); 200 | } 201 | fwrite($handle, "DELIMITER ;\n\n"); 202 | } 203 | $res->close(); 204 | } 205 | 206 | fwrite($handle, "\n"); 207 | } 208 | 209 | 210 | private function delimite(string $s): string 211 | { 212 | return '`' . str_replace('`', '``', $s) . '`'; 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/MySQLImport.php: -------------------------------------------------------------------------------- 1 | connection = $connection; 27 | 28 | if ($connection->connect_errno) { 29 | throw new Exception($connection->connect_error); 30 | 31 | } elseif (!$connection->set_charset($charset)) { // was added in MySQL 5.0.7 and PHP 5.0.5, fixed in PHP 5.1.5) 32 | throw new Exception($connection->error); 33 | } 34 | } 35 | 36 | 37 | /** 38 | * Loads dump from the file. 39 | */ 40 | public function load(string $file): int 41 | { 42 | $handle = strcasecmp(substr($file, -3), '.gz') ? fopen($file, 'rb') : gzopen($file, 'rb'); 43 | if (!$handle) { 44 | throw new Exception("ERROR: Cannot open file '$file'."); 45 | } 46 | return $this->read($handle); 47 | } 48 | 49 | 50 | /** 51 | * Reads dump from logical file. 52 | * @param resource 53 | */ 54 | public function read($handle): int 55 | { 56 | if (!is_resource($handle) || get_resource_type($handle) !== 'stream') { 57 | throw new Exception('Argument must be stream resource.'); 58 | } 59 | 60 | $stat = fstat($handle); 61 | 62 | $sql = ''; 63 | $delimiter = ';'; 64 | $count = $size = 0; 65 | 66 | while (!feof($handle)) { 67 | $s = fgets($handle); 68 | $size += strlen($s); 69 | if (strtoupper(substr($s, 0, 10)) === 'DELIMITER ') { 70 | $delimiter = trim(substr($s, 10)); 71 | 72 | } elseif (substr($ts = rtrim($s), -strlen($delimiter)) === $delimiter) { 73 | $sql .= substr($ts, 0, -strlen($delimiter)); 74 | if (!$this->connection->query($sql)) { 75 | throw new Exception($this->connection->error . ': ' . $sql); 76 | } 77 | $sql = ''; 78 | $count++; 79 | if ($this->onProgress) { 80 | call_user_func($this->onProgress, $count, isset($stat['size']) ? $size * 100 / $stat['size'] : null); 81 | } 82 | 83 | } else { 84 | $sql .= $s; 85 | } 86 | } 87 | 88 | if (rtrim($sql) !== '') { 89 | $count++; 90 | if (!$this->connection->query($sql)) { 91 | throw new Exception($this->connection->error . ': ' . $sql); 92 | } 93 | if ($this->onProgress) { 94 | call_user_func($this->onProgress, $count, isset($stat['size']) ? 100 : null); 95 | } 96 | } 97 | 98 | return $count; 99 | } 100 | } 101 | --------------------------------------------------------------------------------