├── LICENSE ├── composer.json ├── src └── Rah │ └── Danpu │ ├── Exception.php │ ├── BaseInterface.php │ ├── Compress.php │ ├── Import.php │ ├── Dump.php │ ├── Base.php │ ├── Export.php │ └── Config.php ├── CHANGELOG.md └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2018 Jukka Svahn 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 17 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rah/danpu", 3 | "description": "Zero-dependency MySQL dump library for easily exporting and importing databases", 4 | "keywords": ["mysql", "sql", "dump", "backup", "restore", "export", "import", "library", "mysqldump", "database", "db"], 5 | "homepage": "https://github.com/gocom/danpu", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Jukka Svahn", 10 | "homepage": "https://github.com/gocom", 11 | "role": "Developer" 12 | } 13 | ], 14 | "support": { 15 | "issues": "https://github.com/gocom/danpu/issues", 16 | "source": "https://github.com/gocom/danpu" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "Rah\\Danpu\\": "src/Rah/Danpu/" 21 | } 22 | }, 23 | "autoload-dev": { 24 | "psr-4": { 25 | "Rah\\Danpu\\Test\\": "tests/Rah/Danpu/Test/" 26 | } 27 | }, 28 | "require": { 29 | "php": ">=5.3.0", 30 | "ext-pdo": "*" 31 | }, 32 | "suggest": { 33 | "ext-zlib": "*" 34 | }, 35 | "require-dev": { 36 | "phpunit/phpunit": "5.7.*", 37 | "squizlabs/php_codesniffer": "3.*", 38 | "satooshi/php-coveralls": "0.6.*" 39 | }, 40 | "extra": { 41 | "branch-alias": { 42 | "dev-master": "2.x-dev" 43 | } 44 | }, 45 | "scripts": { 46 | "test": "./vendor/bin/phpunit", 47 | "cs": "./vendor/bin/phpcs" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Rah/Danpu/Exception.php: -------------------------------------------------------------------------------- 1 | pdo with new 71 | * PDO connection instance. 72 | * 73 | * @see \PDO 74 | * @throws Exception 75 | * @internal 76 | */ 77 | 78 | public function connect(); 79 | 80 | /** 81 | * Returns a path to the target file. 82 | * 83 | * @return string The path 84 | */ 85 | 86 | public function __toString(); 87 | } 88 | -------------------------------------------------------------------------------- /src/Rah/Danpu/Compress.php: -------------------------------------------------------------------------------- 1 | pack('/source/dump.sql', '/target/dump.sql.gz'); 41 | * ``` 42 | * 43 | * @internal 44 | */ 45 | 46 | class Compress 47 | { 48 | /** 49 | * Constructor. 50 | * 51 | * @throws Exception 52 | * @since 2.3.3 53 | */ 54 | 55 | public function __construct() 56 | { 57 | if (!function_exists('gzopen')) { 58 | throw new Exception('Zlib support is not enabled in PHP. Try uncompressed file.'); 59 | } 60 | } 61 | 62 | /** 63 | * Compresses a file. 64 | * 65 | * @param string $from The source 66 | * @param string $to The target 67 | * @throws Exception 68 | */ 69 | 70 | public function pack($from, $to) 71 | { 72 | if (($gzip = gzopen($to, 'wb')) === false) { 73 | throw new Exception('Unable create compressed file.'); 74 | } 75 | 76 | if (($source = fopen($from, 'rb')) === false) { 77 | throw new Exception('Unable open the compression source file.'); 78 | } 79 | 80 | while (!feof($source)) { 81 | $content = fread($source, 4096); 82 | gzwrite($gzip, $content, strlen($content)); 83 | } 84 | 85 | gzclose($gzip); 86 | fclose($source); 87 | } 88 | 89 | /** 90 | * Uncompresses a file. 91 | * 92 | * @param string $from The source 93 | * @param string $to The target 94 | * @throws Exception 95 | */ 96 | 97 | public function unpack($from, $to) 98 | { 99 | if (($gzip = gzopen($from, 'rb')) === false) { 100 | throw new Exception('Unable to read compressed file.'); 101 | } 102 | 103 | if (($target = fopen($to, 'w')) === false) { 104 | throw new Exception('Unable to open the target.'); 105 | } 106 | 107 | while ($string = gzread($gzip, 4096)) { 108 | fwrite($target, $string, strlen($string)); 109 | } 110 | 111 | gzclose($gzip); 112 | fclose($target); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Rah/Danpu/Import.php: -------------------------------------------------------------------------------- 1 | file('/path/to/target/dump/file.sql') 43 | * ->dsn('mysql:dbname=database;host=localhost') 44 | * ->user('username') 45 | * ->pass('password') 46 | * ->tmp('/tmp'); 47 | * 48 | * new Import($config); 49 | * ``` 50 | */ 51 | 52 | class Import extends Base 53 | { 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | 58 | public function init() 59 | { 60 | $this->connect(); 61 | $this->tmpFile(); 62 | 63 | if (!is_file($this->config->file) || !is_readable($this->config->file)) { 64 | throw new Exception('Unable to access the source file.'); 65 | } 66 | 67 | if ($this->compress) { 68 | $gzip = new Compress(); 69 | $gzip->unpack($this->config->file, $this->temp); 70 | } else { 71 | copy($this->config->file, $this->temp); 72 | } 73 | 74 | $this->open($this->temp, 'r'); 75 | $this->import(); 76 | $this->close(); 77 | $this->clean(); 78 | } 79 | 80 | /** 81 | * Processes the SQL file. 82 | * 83 | * Reads a SQL file by line by line. Expects that 84 | * individual queries are separated by semicolons, 85 | * and that quoted values are properly escaped, 86 | * including newlines. 87 | * 88 | * Queries themselves can not contain any comments. 89 | * All comments are stripped from the file. 90 | */ 91 | 92 | protected function import() 93 | { 94 | $query = ''; 95 | 96 | while (!feof($this->file)) { 97 | $line = fgets($this->file); 98 | $trim = trim($line); 99 | 100 | if ($trim === '' || strpos($trim, '--') === 0 || strpos($trim, '/*') === 0) { 101 | continue; 102 | } 103 | 104 | if (strpos($trim, 'DELIMITER ') === 0) { 105 | $this->delimiter = substr($trim, 10); 106 | continue; 107 | } 108 | 109 | $query .= $line; 110 | 111 | if (substr($trim, strlen($this->delimiter) * -1) === $this->delimiter) { 112 | $this->pdo->exec(substr(trim($query), 0, strlen($this->delimiter) * -1)); 113 | $query = ''; 114 | } 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | Version 2.7.0 - 2018/11/07 5 | ---- 6 | 7 | * Fixed: Test suite compatibility on PHP >= 7.2. 8 | * Added: Create database statement. 9 | * Added: Safe SQL delimiter generation. Checks that the contained query does not contain the delimiter rather than using hard-coded slash or semicolon. 10 | * Added: Invokes informative `E_USER_DEPRECATED` messages when using deprecated configuration options. 11 | * Added: Supports dumping events. 12 | * Added: Checks MySQL version before dumping triggers or events. 13 | * Changed: All configuration values are now strictly type strict. 14 | 15 | Version 2.6.2 - 2013/12/22 16 | ---- 17 | 18 | * Fixed: `Rah\Danpu\Dump::__call()` now properly requires $args, rather than having the second argument optional. 19 | * Changed: Check that the source file exists before trying to import it. 20 | * Started testing the project on [HHVM](http://hhvm.com/); passes all tests. 21 | 22 | Version 2.6.1 - 2013/12/18 23 | ---- 24 | 25 | * Fixed: `Rah\Danpu\Dump::__construct()` `$config` argument. Now properly uses passed `Rah\Danpu\Config` instances. 26 | * Added: Test for test code coverage. 27 | 28 | Version 2.6.0 - 2013/12/16 29 | ---- 30 | 31 | * Added: `Rah\Danpu\BaseInterface`. 32 | * Added: Support for filtering tables by prefix. 33 | * Added: Option to get configuration values from the `Rah\Danpu\Dump` class. 34 | * Added: Generated dump file now ends to linefeed. 35 | * Added: Option to disable auto-commit, foreign and unique key checks. These can be used to generated SQL dumps that import faster to InnoDB tables. 36 | * Changed: Writes the DSN to the dump header instead of the old database property and host. 37 | * Changed: Dump setter and Config values inheritance. Workers now require instance of Dump, but Dump can be fed a different Config instance. This makes sure the methods implemented in the setter, Dump, are available in the consumer class. Extending Config class still works as previously, just pass your Config through Dump to the consumer. 38 | * Changed: Rewritten tests. 39 | * Changed: Improved PHPdoc blocks. 40 | * Changed: Adopted full [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) and [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) standards compliance. 41 | 42 | Version 2.5.0 - 2013/05/22 43 | ---- 44 | 45 | * Fixed: Database table ignoring. 46 | * Fixed: Importer's decompressor. 47 | * Fixed: Table locking doesn't generate errors if the database doesn't have tables. 48 | * Added: Support for triggers and views. 49 | * Added: Importer supports delimiter keyword. 50 | * Changed: Any SQL error is thrown as exception. 51 | 52 | Version 2.4.0 - 2013/05/16 53 | ---- 54 | 55 | * Fixed: Makes sure creating the temporary file was successful. 56 | * Added: Adds completed on line to the dump file. 57 | * Added: Option to only dump table structures and no rows. 58 | 59 | Version 2.3.3 - 2013/05/15 60 | ---- 61 | 62 | * Suggest Zlib, but do not require. Zlib isn't required if the file isn't compressed. 63 | 64 | Version 2.3.2 - 2013/05/14 65 | ---- 66 | 67 | * Fixed: Define Rah\Danpu\Base::$pdo property as protected. 68 | 69 | Version 2.3.1 - 2013/05/14 70 | ---- 71 | 72 | * Fixed: Can pass instance of `Rah\Danpu\Config` to a worker. `Rah\Danpu\Base` now correctly hints `Rah\Danpu\Config` instead of `Rah\Danpu\Dump`. 73 | 74 | Version 2.3.0 - 2013/05/13 75 | ---- 76 | 77 | * Fixed: Errors in the given examples. The `Rah\Danpu\Dump::temp()` should be `Rah\Danpu\Dump::tmp()`. 78 | * Added: `Rah\Danpu\Config` class. Allows creating dump configs by extending. 79 | * Added: `Rah\Danpu\Dump` now validates the given configuration option names. 80 | * Added: [PHPunit](http://phpunit.de) tests and [Travis](https://travis-ci.org/gocom/danpu). 81 | 82 | Version 2.2.0 - 2013/05/13 83 | ---- 84 | 85 | * Added: Database connection details are set using a DSN string. Deprecates `Rah\Danpu\Dump::$db` and `Rah\Danpu\Dump::$host`. 86 | 87 | Version 2.1.1 - 2013/05/13 88 | ---- 89 | 90 | * Fixed: Catch `PDOException`. 91 | 92 | Version 2.1.0 - 2013/05/13 93 | ---- 94 | 95 | * Added: Option to ignore database tables. 96 | 97 | Version 2.0.1 - 2013/05/11 98 | ---- 99 | 100 | * Fixed: Error in the composer.json. 101 | 102 | Version 2.0.0 - 2013/05/11 103 | ---- 104 | 105 | * Initial release. 106 | -------------------------------------------------------------------------------- /src/Rah/Danpu/Dump.php: -------------------------------------------------------------------------------- 1 | dsn('mysql:dbname=database;host=localhost') 42 | * ->file('/path/to/dump.sql'); 43 | * ``` 44 | * 45 | * @method Dump dsn(string $dsn) 46 | * @method Dump user(string $username) 47 | * @method Dump pass(string $password) 48 | * @method Dump attributes(array $attributes) 49 | * @method Dump encoding(string $encoding) 50 | * @method Dump ignore(array $ignoredTablesViews) 51 | * @method Dump tmp(string $directory) 52 | * @method Dump file(string $filename) 53 | * @method Dump data(bool $includeRows) 54 | * @method Dump structure(bool $includeStructure) 55 | * @method Dump triggers(bool $includeTriggers) 56 | * @method Dump prefix(string $filterByTableViewPrefix) 57 | * @method Dump disableForeignKeyChecks(bool $disabled) 58 | * @method Dump disableUniqueKeyChecks(bool $disabled) 59 | * @method Dump disableAutoCommit(bool $disabled) 60 | * @method Dump select(array $rows) 61 | * @property-read string $dsn 62 | * @property-read string $user 63 | * @property-read string $pass 64 | * @property-read array $attributes 65 | * @property-read string $encoding 66 | * @property-read array $ignore 67 | * @property-read string $tmp 68 | * @property-read string $file 69 | * @property-read bool $data 70 | * @property-read bool $triggers 71 | * @property-read string $prefix 72 | * @property-read bool $disableForeignKeyChecks 73 | * @property-read bool $disableUniqueKeyChecks 74 | * @property-read bool $disableAutoCommit 75 | * @property-read array $select 76 | * @see Config 77 | */ 78 | 79 | class Dump 80 | { 81 | /** 82 | * Stores the configuration instance. 83 | * 84 | * @var Config 85 | */ 86 | 87 | protected $config; 88 | 89 | /** 90 | * Constructor. 91 | * 92 | * The constructor can be optionally be given a 93 | * pre-configured Config class instance. If left 94 | * to NULL, it defaults to Config. 95 | * 96 | * The config argument can for instance be your Config 97 | * class implementation that you've created by extending. 98 | * 99 | * ```php 100 | * class MyAppConfig extends \Rah\Danpu\Config 101 | * { 102 | * public $dsn = 'mysql:dbname=database;host=localhost'; 103 | * } 104 | * new Dump(new MyAppConfig); 105 | * ``` 106 | * 107 | * @param Config|null $config The config, defaults to Config 108 | */ 109 | 110 | public function __construct(Config $config = null) 111 | { 112 | if ($config === null) { 113 | $this->config = new Config; 114 | } else { 115 | $this->config = $config; 116 | } 117 | 118 | $this->config->attributes = array( 119 | \PDO::ATTR_ORACLE_NULLS => \PDO::NULL_NATURAL, 120 | \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false, 121 | \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, 122 | \PDO::ATTR_EMULATE_PREPARES => false, 123 | \PDO::ATTR_STRINGIFY_FETCHES => false, 124 | ); 125 | } 126 | 127 | /** 128 | * Gets a configuration property. 129 | * 130 | * ```php 131 | * $dump = new \Rah\Danpu\Dump(); 132 | * echo $dump->name; 133 | * ``` 134 | * 135 | * The method throws an exception if the property does not 136 | * exists. 137 | * 138 | * @param string $name The property 139 | * @return mixed The current configuration value 140 | * @throws Exception 141 | */ 142 | 143 | public function __get($name) 144 | { 145 | if (property_exists($this->config, $name) === false) { 146 | throw new Exception('Unknown property: '.$name); 147 | } 148 | 149 | return $this->config->$name; 150 | } 151 | 152 | /** 153 | * Sets a configuration property. 154 | * 155 | * ```php 156 | * $dump = new \Rah\Danpu\Dump(); 157 | * $dump->name('value'); 158 | * ``` 159 | * 160 | * The method throws an exception if the property does not 161 | * exist. 162 | * 163 | * @param string $name Method 164 | * @param array $args Arguments 165 | * @return Dump 166 | * @throws Exception 167 | */ 168 | 169 | public function __call($name, array $args) 170 | { 171 | if (property_exists($this->config, $name) === false) { 172 | throw new Exception('Unknown property: '.$name); 173 | } 174 | 175 | $this->config->$name = $args[0]; 176 | return $this; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Danpu - MySQL dump tool for PHP 2 | ========= 3 | 4 | [![Build Status](https://img.shields.io/travis/gocom/danpu/master.svg)](https://travis-ci.org/gocom/danpu) [![Coverage Status](https://img.shields.io/coveralls/github/gocom/danpu/master.svg)](https://coveralls.io/r/gocom/danpu?branch=master) [![Packagist](https://img.shields.io/packagist/v/rah/danpu.svg)](https://packagist.org/packages/rah/danpu) 5 | 6 | Danpu is a dependency-free, cross-platform, portable PHP library for backing up MySQL databases. It has no hard dependencies, and is fit for restricted environments where security is key and access is limited. Danpu requires nothing more than access to your database, PDO and a directory it can write the backup to. The script is optimized and has low memory-footprint, allowing it to handle even larger databases. 7 | 8 | Danpu supports backing up table structures, the data itself, views and triggers. Created dump files can optionally be compressed to save space, and generated SQL output is optimized for compatibility. 9 | 10 | Requirements 11 | --------- 12 | 13 | Minimum: 14 | 15 | * PHP 5.3.0 or newer 16 | * MySQL 4.1.0 or newer 17 | * [PDO](https://php.net/pdo) 18 | 19 | Recommended, but optional: 20 | 21 | * PHP 5.4.0 or newer 22 | * MySQL 5.0.11 or newer 23 | * [zlib](https://www.php.net/manual/en/book.zlib.php) 24 | 25 | Backing up views and triggers requires MySQL 5.0.11 or newer. 26 | 27 | Install 28 | --------- 29 | 30 | Using [Composer](https://getcomposer.org): 31 | 32 | $ composer require rah/danpu 33 | 34 | Usage 35 | --------- 36 | 37 | To create a new backup or import one, configure a new ```Dump``` instance and pass it to one of the worker classes. To begin, first make sure you have included Composer's autoloader file in your project: 38 | 39 | ```php 40 | require './vendor/autoload.php'; 41 | ``` 42 | 43 | If you are already using other Composer packages, or a modern Composer-managed framework, this should be taken care of already. If not, merely add the autoloader to your base bootstrap includes. See [Composer's documentation](https://getcomposer.org) for more information. 44 | 45 | ### Take a backup 46 | 47 | Backups can be created with the ```Export``` class. The class exports the database to a SQL file, or throws exceptions on errors. The file is compressed if the target filename ends to a .gz extension. 48 | 49 | ```php 50 | use Rah\Danpu\Dump; 51 | use Rah\Danpu\Export; 52 | 53 | try { 54 | $dump = new Dump; 55 | $dump 56 | ->file('/path/to/target/dump/file.sql') 57 | ->dsn('mysql:dbname=database;host=localhost') 58 | ->user('username') 59 | ->pass('password') 60 | ->tmp('/tmp'); 61 | 62 | new Export($dump); 63 | } catch (\Exception $e) { 64 | echo 'Export failed with message: ' . $e->getMessage(); 65 | } 66 | ``` 67 | 68 | The database is dumped in chunks, one row at the time without buffering huge amount of data to the memory. This makes the script very memory efficient, and can allow Danpu to handle databases of any size, given the system limitations of course. You physically won't be able backup rows that take more memory than can be allocated to PHP, nor write backups if there isn't enough space for the files. 69 | 70 | ### Import a backup 71 | 72 | Danpu can also import its own backups using the Import class. While the importer works with backups it has made, it doesn't accept freely formatted SQL. The importer is pretty strict about formatting, and expects exactly the same format as generated by Danpu. It expects that values in statements are escaped properly, including newlines, queries have to end to a semicolon and statements preferably should not wrap to multiple lines. 73 | 74 | To import a backup, create a new instance of the Import class. It uncompresses any .gz files before importing. 75 | 76 | ```php 77 | use Rah\Danpu\Dump; 78 | use Rah\Danpu\Import; 79 | 80 | try { 81 | $dump = new Dump; 82 | $dump 83 | ->file('/path/to/imported/file.sql') 84 | ->dsn('mysql:dbname=database;host=localhost') 85 | ->user('username') 86 | ->pass('password') 87 | ->tmp('/tmp'); 88 | 89 | new Import($dump); 90 | } catch (\Exception $e) { 91 | echo 'Import failed with message: ' . $e->getMessage(); 92 | } 93 | ``` 94 | 95 | ### Options 96 | 97 | In addition to the mandatory connection and file location, Danpu accepts various optional configuration options. These include filtering tables and views by prefix, ignoring tables and creating dumps without row data. See the [src/Rah/Danpu/Config.php](https://github.com/gocom/danpu/blob/master/src/Rah/Danpu/Config.php) for full list of options. The source file contains detailed documentation blocks, outlining each option. 98 | 99 | Troubleshooting 100 | --------- 101 | 102 | ### Running out of memory, backup taking too long 103 | 104 | As with any PHP script, Danpu is restricted by the safe limits you have set for PHP. When dealing with larger databases, taking a backup will take longer and require more memory. If you find yourself hitting the maximum execution time or running out of memory, its time to increase the limits enough to let the script to work. 105 | 106 | PHP lets you to change these limits with its [configuration options](https://php.net/manual/en/ini.core.php), which can be modified [temporarily during script execution](https://php.net/manual/en/function.ini-set.php), or in global configuration files. The configuration options you will be the most interested in, are [memory_limit](https://www.php.net/manual/en/ini.core.php#ini.memory-limit) and [max_execution_time](https://www.php.net/manual/en/info.configuration.php#ini.max-execution-time), and possibly [ignore_user_abort](https://php.net/manual/en/function.ignore-user-abort.php). You can change these values in your script before running a backup with Danpu: 107 | 108 | ```php 109 | ini_set('memory_limit', '256M'); 110 | set_time_limit(0); 111 | ignore_user_abort(true); 112 | ``` 113 | 114 | This of course requires that you have access to these values and you actually have more memory to give. Keep in mind that PHP may be affected by other limitations, such as your web server. 115 | -------------------------------------------------------------------------------- /src/Rah/Danpu/Base.php: -------------------------------------------------------------------------------- 1 | config = $config; 122 | $this->compress = pathinfo($this->config->file, PATHINFO_EXTENSION) === 'gz'; 123 | $this->init(); 124 | } 125 | 126 | /** 127 | * {@inheritdoc} 128 | */ 129 | 130 | public function __destruct() 131 | { 132 | $this->close(); 133 | $this->clean(); 134 | $this->unlock(); 135 | } 136 | 137 | /** 138 | * {@inheritdoc} 139 | */ 140 | 141 | public function connect() 142 | { 143 | if ($this->config->dsn === null && $this->config->db !== null) { 144 | trigger_error('Config::$db is deprecated, see Config::$dsn.', E_USER_DEPRECATED); 145 | $this->config->dsn("mysql:dbname={$this->config->db};host={$this->config->host}"); 146 | } 147 | 148 | try { 149 | $this->pdo = new \PDO( 150 | $this->config->dsn, 151 | $this->config->user, 152 | $this->config->pass 153 | ); 154 | 155 | $this->pdo->exec('SET NAMES '.$this->config->encoding); 156 | 157 | foreach ($this->config->attributes as $name => $value) { 158 | $this->pdo->setAttribute($name, $value); 159 | } 160 | 161 | $sth = $this->pdo->query('SELECT DATABASE() FROM DUAL'); 162 | $database = $sth->fetch(); 163 | $this->database = end($database); 164 | $this->version = (string) $this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION); 165 | } catch (\PDOException $e) { 166 | throw new Exception('Connecting to database failed with message: '.$e->getMessage()); 167 | } 168 | } 169 | 170 | /** 171 | * Gets tables. 172 | */ 173 | 174 | protected function getTables() 175 | { 176 | $this->tables = $this->pdo->prepare('SHOW FULL TABLES'); 177 | } 178 | 179 | /** 180 | * Locks all tables. 181 | * 182 | * @return bool 183 | */ 184 | 185 | protected function lock() 186 | { 187 | $this->tables->execute(); 188 | $table = array(); 189 | 190 | while ($a = $this->tables->fetch(\PDO::FETCH_ASSOC)) { 191 | $table[] = current($a); 192 | } 193 | 194 | return !$table || $this->pdo->exec('LOCK TABLES `'.implode('` WRITE, `', $table).'` WRITE'); 195 | } 196 | 197 | /** 198 | * Unlocks all tables. 199 | * 200 | * @return bool 201 | */ 202 | 203 | protected function unlock() 204 | { 205 | return $this->pdo->exec('UNLOCK TABLES'); 206 | } 207 | 208 | /** 209 | * Gets a path to a temporary file acting as a buffer. 210 | * 211 | * @throws Exception 212 | * @since 2.4.0 213 | */ 214 | 215 | protected function tmpFile() 216 | { 217 | if (($this->temp = tempnam($this->config->tmp, 'Rah_Danpu_')) === false) { 218 | throw new Exception('Unable to create a temporary file, check the configured tmp directory.'); 219 | } 220 | } 221 | 222 | /** 223 | * Cleans left over temporary file trash. 224 | * 225 | * @since 2.4.0 226 | */ 227 | 228 | protected function clean() 229 | { 230 | if (file_exists($this->temp)) { 231 | unlink($this->temp); 232 | } 233 | } 234 | 235 | /** 236 | * Opens a file for writing. 237 | * 238 | * @param string $filename The filename 239 | * @param string $flags Flags 240 | * @throws Exception 241 | */ 242 | 243 | protected function open($filename, $flags) 244 | { 245 | if (is_file($filename) === false || ($this->file = fopen($filename, $flags)) === false) { 246 | throw new Exception('Unable to open the target file.'); 247 | } 248 | } 249 | 250 | /** 251 | * Closes a file pointer. 252 | */ 253 | 254 | protected function close() 255 | { 256 | if (is_resource($this->file)) { 257 | fclose($this->file); 258 | } 259 | } 260 | 261 | /** 262 | * Writes a line to the file. 263 | * 264 | * @param string $string The string to write 265 | * @param bool $format Format the string 266 | */ 267 | 268 | protected function write($string, $format = true) 269 | { 270 | if ($format) { 271 | $string .= $this->delimiter; 272 | } 273 | 274 | $string .= "\n"; 275 | 276 | if (fwrite($this->file, $string, strlen($string)) === false) { 277 | throw new Exception('Unable to write '.strlen($string).' bytes to the dumpfile.'); 278 | } 279 | } 280 | 281 | /** 282 | * Moves a temporary file to the final location. 283 | * 284 | * @return bool 285 | * @throws Exception 286 | */ 287 | 288 | protected function move() 289 | { 290 | if ($this->compress) { 291 | $gzip = new Compress($this->config); 292 | $gzip->pack($this->temp, $this->config->file); 293 | unlink($this->temp); 294 | return true; 295 | } 296 | 297 | if (@rename($this->temp, $this->config->file)) { 298 | return true; 299 | } 300 | 301 | if (@copy($this->temp, $this->config->file) && unlink($this->temp)) { 302 | return true; 303 | } 304 | 305 | throw new Exception('Unable to move the temporary file.'); 306 | } 307 | 308 | /** 309 | * Gets a SQL delimiter. 310 | * 311 | * Gives out a character sequence that isn't 312 | * in the given query. 313 | * 314 | * @param string $delimiter Delimiter character 315 | * @param string|null $query The query to check 316 | * @return string Unique delimiter character sequence 317 | * @since 2.7.0 318 | */ 319 | 320 | protected function getDelimiter($delimiter = ';', $query = null) 321 | { 322 | while (1) { 323 | if ($query === null || strpos($query, $delimiter) === false) { 324 | return $delimiter; 325 | } 326 | 327 | $delimiter .= $delimiter; 328 | } 329 | } 330 | 331 | /** 332 | * {@inheritdoc} 333 | */ 334 | 335 | public function __toString() 336 | { 337 | return (string) $this->config->file; 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /src/Rah/Danpu/Export.php: -------------------------------------------------------------------------------- 1 | file('/path/to/target/dump/file.sql') 43 | * ->dsn('mysql:dbname=database;host=localhost') 44 | * ->user('username') 45 | * ->pass('password') 46 | * ->tmp('/tmp'); 47 | * 48 | * new Export($config); 49 | * ``` 50 | */ 51 | 52 | class Export extends Base 53 | { 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | 58 | public function init() 59 | { 60 | $this->connect(); 61 | $this->tmpFile(); 62 | $this->open($this->temp, 'wb'); 63 | $this->getTables(); 64 | $this->lock(); 65 | $this->dump(); 66 | $this->unlock(); 67 | $this->close(); 68 | $this->move(); 69 | } 70 | 71 | /** 72 | * Escapes a value for a use in a SQL statement. 73 | * 74 | * @param mixed $value 75 | * @return string 76 | */ 77 | 78 | protected function escape($value) 79 | { 80 | if ($value === null) { 81 | return 'NULL'; 82 | } 83 | 84 | if (is_integer($value) || is_float($value)) { 85 | return $value; 86 | } 87 | 88 | return $this->pdo->quote($value); 89 | } 90 | 91 | /** 92 | * Dumps database contents to a temporary file. 93 | */ 94 | 95 | protected function dump() 96 | { 97 | $this->write('-- '.date('c').' - '.$this->config->dsn, false); 98 | 99 | if ($this->config->disableAutoCommit === true) { 100 | $this->write('SET AUTOCOMMIT = 0'); 101 | } 102 | 103 | if ($this->config->disableForeignKeyChecks === true) { 104 | $this->write('SET FOREIGN_KEY_CHECKS = 0'); 105 | } 106 | 107 | if ($this->config->disableUniqueKeyChecks === true) { 108 | $this->write('SET UNIQUE_CHECKS = 0'); 109 | } 110 | 111 | if ($this->config->createDatabase) { 112 | if ($this->config->createDatabase === true) { 113 | $database = $this->database; 114 | } else { 115 | $database = (string) $this->config->createDatabase; 116 | } 117 | 118 | $this->write( 119 | 'CREATE DATABASE IF NOT EXISTS `'.$database.'` '. 120 | 'DEFAULT CHARACTER SET = '.$this->escape($this->config->encoding) 121 | ); 122 | $this->write('USE `'.$database.'`'); 123 | } 124 | 125 | $this->dumpTables(); 126 | $this->dumpViews(); 127 | $this->dumpTriggers(); 128 | $this->dumpEvents(); 129 | 130 | if ($this->config->disableForeignKeyChecks === true) { 131 | $this->write('SET FOREIGN_KEY_CHECKS = 1'); 132 | } 133 | 134 | if ($this->config->disableUniqueKeyChecks === true) { 135 | $this->write('SET UNIQUE_CHECKS = 1'); 136 | } 137 | 138 | if ($this->config->disableAutoCommit === true) { 139 | $this->write('COMMIT'); 140 | $this->write('SET AUTOCOMMIT = 1'); 141 | } 142 | 143 | $this->write("\n-- Completed on: ".date('c'), false); 144 | } 145 | 146 | /** 147 | * Dumps tables. 148 | * 149 | * @since 2.5.0 150 | */ 151 | 152 | protected function dumpTables() 153 | { 154 | $this->tables->execute(); 155 | 156 | foreach ($this->tables->fetchAll(\PDO::FETCH_ASSOC) as $a) { 157 | $table = current($a); 158 | 159 | if (isset($a['Table_type']) && $a['Table_type'] === 'VIEW') { 160 | continue; 161 | } 162 | 163 | if (in_array($table, (array) $this->config->ignore, true)) { 164 | continue; 165 | } 166 | 167 | if ((string) $this->config->prefix !== '' && strpos($table, $this->config->prefix) !== 0) { 168 | continue; 169 | } 170 | 171 | if ($this->config->structure === true) { 172 | $structure = $this->pdo->query('SHOW CREATE TABLE `'.$table.'`')->fetch(\PDO::FETCH_ASSOC); 173 | 174 | $this->write("\n-- Table structure for table `{$table}`\n", false); 175 | $this->write('DROP TABLE IF EXISTS `'.$table.'`'); 176 | $this->write(end($structure)); 177 | } 178 | 179 | if ($this->config->data === true) { 180 | $this->write("\n-- Dumping data for table `{$table}`\n", false); 181 | $this->write("LOCK TABLES `{$table}` WRITE"); 182 | 183 | if (isset($this->config->select[$table])) { 184 | $query = $this->config->select[$table]; 185 | } else { 186 | $query = 'SELECT * FROM `'.$table.'`'; 187 | } 188 | 189 | $rows = $this->pdo->prepare($query); 190 | $rows->execute(); 191 | 192 | while ($a = $rows->fetch(\PDO::FETCH_ASSOC)) { 193 | $this->write( 194 | "INSERT INTO `{$table}` VALUES (". 195 | implode(',', array_map(array($this, 'escape'), $a)). 196 | ")" 197 | ); 198 | } 199 | 200 | $this->write('UNLOCK TABLES'); 201 | } 202 | } 203 | } 204 | 205 | /** 206 | * Dumps views. 207 | * 208 | * @since 2.5.0 209 | */ 210 | 211 | protected function dumpViews() 212 | { 213 | $this->tables->execute(); 214 | 215 | foreach ($this->tables->fetchAll(\PDO::FETCH_ASSOC) as $a) { 216 | $view = current($a); 217 | 218 | if (!isset($a['Table_type']) || $a['Table_type'] !== 'VIEW') { 219 | continue; 220 | } 221 | 222 | if (in_array($view, (array) $this->config->ignore, true)) { 223 | continue; 224 | } 225 | 226 | if ((string) $this->config->prefix !== '' && strpos($view, $this->config->prefix) !== 0) { 227 | continue; 228 | } 229 | 230 | $structure = $this->pdo->query('SHOW CREATE VIEW `'.$view.'`'); 231 | 232 | if ($structure = $structure->fetch(\PDO::FETCH_ASSOC)) { 233 | if (isset($structure['Create View'])) { 234 | $this->write("\n-- Structure for view `{$view}`\n", false); 235 | $this->write('DROP VIEW IF EXISTS `'.$view.'`'); 236 | $this->write($structure['Create View']); 237 | } 238 | } 239 | } 240 | } 241 | 242 | /** 243 | * Dumps triggers. 244 | * 245 | * @since 2.5.0 246 | */ 247 | 248 | protected function dumpTriggers() 249 | { 250 | if ($this->config->triggers === true && version_compare($this->version, '5.0.10') >= 0) { 251 | $triggers = $this->pdo->prepare('SHOW TRIGGERS'); 252 | $triggers->execute(); 253 | 254 | while ($a = $triggers->fetch(\PDO::FETCH_ASSOC)) { 255 | if (in_array($a['Table'], (array) $this->config->ignore, true)) { 256 | continue; 257 | } 258 | 259 | if ((string) $this->config->prefix !== '' && strpos($a['Table'], $this->config->prefix) !== 0) { 260 | continue; 261 | } 262 | 263 | $this->write("\n-- Trigger structure `{$a['Trigger']}`\n", false); 264 | $this->write('DROP TRIGGER IF EXISTS `'.$a['Trigger'].'`'); 265 | 266 | $query = "CREATE TRIGGER `{$a['Trigger']}`". 267 | " {$a['Timing']} {$a['Event']} ON `{$a['Table']}`". 268 | " FOR EACH ROW\n{$a['Statement']}"; 269 | 270 | $delimiter = $this->getDelimiter('//', $query); 271 | $this->write("DELIMITER {$delimiter}\n{$query}\n{$delimiter}\nDELIMITER ;", false); 272 | } 273 | } 274 | } 275 | 276 | /** 277 | * Dumps events. 278 | * 279 | * @since 2.7.0 280 | */ 281 | 282 | protected function dumpEvents() 283 | { 284 | if ($this->config->events === true && version_compare($this->version, '5.1.12') >= 0) { 285 | $events = $this->pdo->prepare('SHOW EVENTS'); 286 | $events->execute(); 287 | 288 | foreach ($events->fetchAll(\PDO::FETCH_ASSOC) as $a) { 289 | $event = $a['Name']; 290 | 291 | if (in_array($event, (array) $this->config->ignore, true)) { 292 | continue; 293 | } 294 | 295 | if ((string) $this->config->prefix !== '' && strpos($event, $this->config->prefix) !== 0) { 296 | continue; 297 | } 298 | 299 | $structure = $this->pdo->query('SHOW CREATE EVENT `'.$event.'`'); 300 | 301 | if ($structure = $structure->fetch(\PDO::FETCH_ASSOC)) { 302 | if (isset($structure['Create Event'])) { 303 | $query = $structure['Create Event']; 304 | $delimiter = $this->getDelimiter('//', $query); 305 | $this->write("\n-- Structure for event `{$event}`\n", false); 306 | $this->write('DROP EVENT IF EXISTS `'.$event.'`'); 307 | $this->write("DELIMITER {$delimiter}\n{$query}\n{$delimiter}\nDELIMITER ;", false); 308 | } 309 | } 310 | } 311 | } 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /src/Rah/Danpu/Config.php: -------------------------------------------------------------------------------- 1 | file('/path/to/target/dump/file.sql') 48 | * ->dsn('mysql:dbname=database;host=localhost') 49 | * ->user('username') 50 | * ->pass('password') 51 | * ->tmp('/tmp'); 52 | * ``` 53 | * 54 | * When done pass the instance to a worker class such as Export 55 | * through the constructor. 56 | * 57 | * ```php 58 | * new \Rah\Danpu\Export($dump); 59 | * ``` 60 | * 61 | * Alternative to Dump class, the Config class can be extended. 62 | * 63 | * ```php 64 | * namespace App\Dump; 65 | * class Config extends \Rah\Danpu\Config 66 | * { 67 | * public $file = '/path/to/dump.sql'; 68 | * public $dsn = 'mysql:dbname=database;host=localhost'; 69 | * public $user = 'username'; 70 | * public $pass = 'password'; 71 | * public $tmp = '/tmp'; 72 | * } 73 | * ``` 74 | * 75 | * Extending could be used to generate application wide 76 | * pre-populated configuration sets. Just pass an instance of 77 | * your config class to a worker class through Dump: 78 | * 79 | * ```php 80 | * use App\Dump\Config; 81 | * use Rah\Danpu\Export; 82 | * use Rah\Danpu\Dump; 83 | * new Export(new Dump(new Config)); 84 | * ``` 85 | * 86 | * @since 2.3.0 87 | * @see Dump 88 | */ 89 | 90 | class Config 91 | { 92 | /** 93 | * Data source name. 94 | * 95 | * The DSN used to connect to the database. Basically specifies 96 | * the database location. In general, a DSN consists of the PDO driver name, 97 | * followed by a colon, followed by the PDO driver-specific connection syntax. 98 | * 99 | * For instance to connect to a MySQL database hosted locally: 100 | * 101 | * ```php 102 | * $dump = new \Rah\Danpu\Dump(); 103 | * $dump->dsn('mysql:dbname=database;host=localhost'); 104 | * ``` 105 | * 106 | * Where 'database' is the name of the database and 'localhost' 107 | * is the hostname. 108 | * 109 | * @var string 110 | * @since 2.2.0 111 | * @link https://www.php.net/manual/en/ref.pdo-mysql.connection.php 112 | */ 113 | 114 | public $dsn; 115 | 116 | /** 117 | * The username used to connect to the database. 118 | * 119 | * ```php 120 | * $dump = new \Rah\Danpu\Dump(); 121 | * $dump->user('DatabaseUsername'); 122 | * ``` 123 | * 124 | * @var string 125 | */ 126 | 127 | public $user; 128 | 129 | /** 130 | * The password used to connect to the database. 131 | * 132 | * Database user's password. Defaults to 133 | * an empty string. 134 | * 135 | * ```php 136 | * $dump = new \Rah\Danpu\Dump(); 137 | * $dump->password('DatabasePassword'); 138 | * ``` 139 | * 140 | * @var string 141 | */ 142 | 143 | public $pass = ''; 144 | 145 | /** 146 | * Connection attributes. 147 | * 148 | * An array of driver-specific connection options. This 149 | * affect to the connection that is used for taking 150 | * the backup. 151 | * 152 | * For instance, you can use this to increase the 153 | * timeout limit if its too little. 154 | * 155 | * ```php 156 | * $dump = new \Rah\Danpu\Dump(); 157 | * $dump->attributes(array( 158 | * \PDO::ATTR_TIMEOUT => 900, 159 | * )); 160 | * ``` 161 | * 162 | * @var array 163 | */ 164 | 165 | public $attributes = array(); 166 | 167 | /** 168 | * Database encoding. 169 | * 170 | * Set this to what your data in your 171 | * database uses. Defaults to 'utf8'. 172 | * 173 | * ```php 174 | * $dump = new \Rah\Danpu\Dump(); 175 | * $dump->encoding('utf16'); 176 | * ``` 177 | * 178 | * To minimize issues, don't mix different encodings 179 | * in your database. All data should be encoded 180 | * using the same. 181 | * 182 | * @var string 183 | */ 184 | 185 | public $encoding = 'utf8'; 186 | 187 | /** 188 | * An array of ignored tables, views and triggers based on the target table. 189 | * 190 | * This can be used to exclude confidential or temporary 191 | * data from the backup, like passwords and sessions values. 192 | * 193 | * ```php 194 | * $dump = new \Rah\Danpu\Dump(); 195 | * $dump->ignore(array('user_sessions', 'user_credentials')); 196 | * ``` 197 | * 198 | * @var array 199 | * @since 2.1.0 200 | */ 201 | 202 | public $ignore = array(); 203 | 204 | /** 205 | * A prefix used by tables, views and triggers based on the target table. 206 | * 207 | * Taken backup will only include items that start 208 | * with the prefix. 209 | * 210 | * ```php 211 | * $dump = new \Rah\Danpu\Dump(); 212 | * $dump->prefix('user_'); 213 | * ``` 214 | * 215 | * @var string 216 | * @since 2.6.0 217 | */ 218 | 219 | public $prefix; 220 | 221 | /** 222 | * Temporary directory. 223 | * 224 | * Absolute path to the temporary directory without 225 | * trailing slash. Defaults to '/tmp'. 226 | * 227 | * This directory is used as a temporary storage for 228 | * writing the backup, a on-disk buffer so to speak. 229 | * 230 | * ```php 231 | * $dump = new \Rah\Danpu\Dump(); 232 | * $dump->tmp('/path/to/temporary/directory'); 233 | * ``` 234 | * 235 | * This directory must be writable and private. You 236 | * may not want to use a virtual one stored in memory, 237 | * given that we are writing your database backup 238 | * in there, and it might be a large one. 239 | * 240 | * @var string 241 | */ 242 | 243 | public $tmp = '/tmp'; 244 | 245 | /** 246 | * The target SQL dump file. 247 | * 248 | * Your backup is written to the specified 249 | * location. To enable Gzipping, add '.gz' extension 250 | * to the filename. 251 | * 252 | * ```php 253 | * $dump = new \Rah\Danpu\Dump(); 254 | * $dump->file('/path/to/dump.sql'); 255 | * ``` 256 | * 257 | * @var string 258 | */ 259 | 260 | public $file; 261 | 262 | /** 263 | * Dump table data. 264 | * 265 | * Set FALSE to only dump structure. No 266 | * data and inserts will be added. 267 | * 268 | * ```php 269 | * $dump = new \Rah\Danpu\Dump(); 270 | * $dump->data(false); 271 | * ``` 272 | * 273 | * @var bool 274 | * @since 2.4.0 275 | */ 276 | 277 | public $data = true; 278 | 279 | /** 280 | * Dump table structure. 281 | * 282 | * Set FALSE to only dump table data. 283 | * 284 | * ```php 285 | * $dump = new \Rah\Danpu\Dump(); 286 | * $dump->structure(false); 287 | * ``` 288 | * 289 | * @var bool 290 | * @since 2.7.0 291 | */ 292 | 293 | public $structure = true; 294 | 295 | /** 296 | * Dump triggers. 297 | * 298 | * Set FALSE to skip triggers. The dump 299 | * file will not contain any triggers. 300 | * 301 | * ```php 302 | * $dump = new \Rah\Danpu\Dump(); 303 | * $dump->trigger(false); 304 | * ``` 305 | * 306 | * @var bool 307 | * @since 2.5.0 308 | */ 309 | 310 | public $triggers = true; 311 | 312 | /** 313 | * Dump events. 314 | * 315 | * Set FALSE to skip events. The dump 316 | * file will not contain any events. 317 | * 318 | * ```php 319 | * $dump = new \Rah\Danpu\Dump(); 320 | * $dump->events(false); 321 | * ``` 322 | * 323 | * @var bool 324 | * @since 2.7.0 325 | */ 326 | 327 | public $events = true; 328 | 329 | /** 330 | * Enables dumping the database create statement. 331 | * 332 | * Set to TRUE to add create database statement 333 | * to the created SQL dump file. 334 | * 335 | * ```php 336 | * $dump = new \Rah\Danpu\Dump(); 337 | * $dump->createDatabase(true); 338 | * ``` 339 | * 340 | * Optionally a new name can be given for the database: 341 | * 342 | * ```php 343 | * $dump = new \Rah\Danpu\Dump(); 344 | * $dump->createDatabase('newdb'); 345 | * ``` 346 | * 347 | * @var bool|string 348 | * @since 2.7.0 349 | */ 350 | 351 | public $createDatabase = false; 352 | 353 | /** 354 | * Disables foreign key checks. 355 | * 356 | * Set TRUE to disable checks. The generated dump 357 | * file will contain statements that temporarily disable 358 | * unique key checks. This will speed up large data 359 | * imports to InnoDB tables. 360 | * 361 | * ```php 362 | * $dump = new \Rah\Danpu\Dump(); 363 | * $dump->disableForeignKeyChecks(true); 364 | * ``` 365 | * 366 | * @var bool 367 | * @since 2.6.0 368 | */ 369 | 370 | public $disableForeignKeyChecks = false; 371 | 372 | /** 373 | * Disables unique key checks. 374 | * 375 | * Set TRUE to disable checks. The generated dump 376 | * file will contain statements that temporarily disable 377 | * unique key checks. This will speed up large data 378 | * imports to InnoDB tables. 379 | * 380 | * ```php 381 | * $dump = new \Rah\Danpu\Dump(); 382 | * $dump->disableUniqueKeyChecks(true); 383 | * ``` 384 | * 385 | * @var bool 386 | * @since 2.6.0 387 | */ 388 | 389 | public $disableUniqueKeyChecks = false; 390 | 391 | /** 392 | * Disables auto-commit mode. 393 | * 394 | * Set TRUE to disable automatic commits. This will speed up 395 | * large data imports to InnoDB tables as each commit is not 396 | * written to the disk right after. 397 | * 398 | * When the generated dump is imported, MySQL is instructed 399 | * to do the actions in memory and write them to the disk 400 | * only once the dump has been successfully processed. This 401 | * option will not work if the import is larger than there 402 | * is memory to be allocated on the system where the 403 | * resulting backup is ran at. 404 | * 405 | * ```php 406 | * $dump = new \Rah\Danpu\Dump(); 407 | * $dump->disableAutoCommit(true); 408 | * ``` 409 | * 410 | * @var bool 411 | * @since 2.6.0 412 | */ 413 | 414 | public $disableAutoCommit = false; 415 | 416 | /** 417 | * An array of select statement used to filter rows. 418 | * 419 | * Only the rows matching the specified statement will be backed up. The 420 | * key is the name of the table and the value is the select statement. 421 | * 422 | * ```php 423 | * $dump = new \Rah\Danpu\Dump(); 424 | * $dump->select(array( 425 | * 'orders' => 'select * from orders where date >= 2017-01-01' 426 | * )); 427 | * ``` 428 | * 429 | * @var array 430 | * @since 2.8.0 431 | */ 432 | 433 | public $select = array(); 434 | 435 | /** 436 | * The database name. 437 | * 438 | * @var string 439 | * @deprecated 2.2.0 440 | */ 441 | 442 | public $db; 443 | 444 | /** 445 | * The hostname. 446 | * 447 | * @var string 448 | * @deprecated 2.2.0 449 | */ 450 | 451 | public $host = 'localhost'; 452 | } 453 | --------------------------------------------------------------------------------