Completed
Push — master ( 8d7d04...c6b8da )
by Sebastian
29s queued 12s
created

Mysqldump::backup()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 37
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 7

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 7
eloc 20
nc 8
nop 2
dl 0
loc 37
ccs 19
cts 19
cp 1
crap 7
rs 8.6666
c 3
b 0
f 0
1
<?php
2
namespace phpbu\App\Backup\Source;
3
4
use phpbu\App\Backup\Restore\Plan;
5
use phpbu\App\Backup\Target;
6
use phpbu\App\Cli\Executable;
7
use phpbu\App\Exception;
8
use phpbu\App\Result;
9
use phpbu\App\Util;
10
11
/**
12
 * Mysqldump source class.
13
 *
14
 * @package    phpbu
15
 * @subpackage Backup
16
 * @author     Sebastian Feldmann <[email protected]>
17
 * @copyright  Sebastian Feldmann <[email protected]>
18
 * @license    https://opensource.org/licenses/MIT The MIT License (MIT)
19
 * @link       http://phpbu.de/
20
 * @since      Class available since Release 1.0.0
21
 */
22
class Mysqldump extends SimulatorExecutable implements Simulator, Restorable
23
{
24
    /**
25
     * Path to mysql executable.
26
     *
27
     * @var string
28
     */
29
    private $pathToMysql;
30
31
    /**
32
     * Path to mysqldump executable.
33
     *
34
     * @var string
35
     */
36
    private $pathToMysqldump;
37
38
    /**
39
     * Path to mysqlimport executable.
40
     *
41
     * @var string
42
     */
43
    private $pathToMysqlimport;
44
45
    /**
46
     * Host to connect to
47
     * --host <hostname>
48
     *
49
     * @var string
50
     */
51
    private $host;
52
53
    /**
54
     * Port to connect to
55
     * --port <port>
56
     *
57
     * @var int
58
     */
59
    private $port;
60
61
    /**
62
     * Port to connect to
63
     * --protocol <TCP|SOCKET|PIPE|MEMORY>
64
     *
65
     * @var string
66
     */
67
    private $protocol;
68
69
    /**
70
     * User to connect with
71
     * --user <username>
72
     *
73
     * @var string
74
     */
75
    private $user;
76
77
    /**
78
     * Password to authenticate with
79
     * --password <password>
80
     *
81
     * @var string
82
     */
83
    private $password;
84
85
    /**
86
     * List of tables to backup
87
     * --tables array of strings
88
     *
89
     * @var array
90
     */
91
    private $tables;
92
93
    /**
94
     * List of databases to backup
95
     * --databases array of strings
96
     *
97
     * @var array
98
     */
99
    private $databases;
100
101
    /**
102
     * List of tables to ignore
103
     *
104
     * @var array
105
     */
106
    private $ignoreTables;
107
108
    /**
109
     * List of tables where only the table structure is stored
110
     *
111
     * @var array
112
     */
113
    private $structureOnly;
114
115
    /**
116
     * Table separated data files
117
     * --tab
118
     *
119
     * @var boolean
120
     */
121
    private $filePerTable;
122
123
    /**
124
     * Use mysqldump quick mode
125
     * -q
126
     *
127
     * @var boolean
128
     */
129
    private $quick;
130
131
    /**
132
     * Lock tables option
133
     * --lock-tables
134
     *
135
     * @var bool
136
     */
137
    private $lockTables;
138
139
    /**
140
     * Single Transaction option
141
     * --single-transaction
142
     *
143
     * @var bool
144
     */
145
    private $singleTransaction;
146
147
    /**
148
     * Use mysqldump with compression
149
     * -C
150
     *
151
     * @var boolean
152
     */
153
    private $compress;
154
155
    /**
156
     * Use mysqldump without extended insert
157
     * --skip-extended-insert
158
     *
159
     * @var boolean
160
     */
161
    private $skipExtendedInsert;
162
163
    /**
164
     * Dump blob fields as hex
165
     * --hex-blob
166
     *
167
     * @var boolean
168
     */
169
    private $hexBlob;
170
171
    /**
172
     * Dump only table structures
173
     * --no-data
174
     *
175
     * @var boolean
176
     */
177
    private $noData;
178
179
    /**
180
     * Add general transaction id statement
181
     * --set-gids-purged=['ON', 'OFF', 'AUTO']
182
     *
183
     * @var string
184
     */
185
    private $gtidPurged;
186
187
    /**
188
     * SSL CA
189
     * --ssl-ca
190
     *
191
     * @var string
192
     */
193
    private $sslCa;
194
195
    /**
196
     * Dump procedures and functions
197
     * --routines
198
     *
199
     * @var bool
200
     */
201
    private $routines;
202
203
    /**
204
     * Skip triggers
205
     * --skip-triggers
206
     *
207
     * @var bool
208
     */
209
    private $skipTriggers;
210 18
211
    /**
212 18
     * Setup.
213
     *
214 18
     * @see    \phpbu\App\Backup\Source
215 18
     * @param  array $conf
216 18
     * @throws \phpbu\App\Exception
217 18
     */
218 18
    public function setup(array $conf = [])
219 18
    {
220 18
        $this->setupSourceData($conf);
221 18
222 18
        $this->pathToMysql        = Util\Arr::getValue($conf, 'pathToMysql', '');
223 18
        $this->pathToMysqldump    = Util\Arr::getValue($conf, 'pathToMysqldump', '');
224 18
        $this->pathToMysqlimport  = Util\Arr::getValue($conf, 'pathToMysqlimport', '');
225 18
        $this->host               = Util\Arr::getValue($conf, 'host', '');
226 18
        $this->port               = Util\Arr::getValue($conf, 'port', 0);
227 18
        $this->protocol           = Util\Arr::getValue($conf, 'protocol', '');
228 18
        $this->user               = Util\Arr::getValue($conf, 'user', '');
229 18
        $this->password           = Util\Arr::getValue($conf, 'password', '');
230 18
        $this->gtidPurged         = Util\Arr::getValue($conf, 'gtidPurged', '');
231 18
        $this->sslCa              = Util\Arr::getValue($conf, 'sslCa', '');
232 18
        $this->hexBlob            = Util\Str::toBoolean(Util\Arr::getValue($conf, 'hexBlob', ''), false);
233
        $this->quick              = Util\Str::toBoolean(Util\Arr::getValue($conf, 'quick', ''), false);
234
        $this->lockTables         = Util\Str::toBoolean(Util\Arr::getValue($conf, 'lockTables', ''), false);
235 18
        $this->singleTransaction  = Util\Str::toBoolean(Util\Arr::getValue($conf, 'singleTransaction', ''), false);
236 1
        $this->compress           = Util\Str::toBoolean(Util\Arr::getValue($conf, 'compress', ''), false);
237
        $this->skipExtendedInsert = Util\Str::toBoolean(Util\Arr::getValue($conf, 'skipExtendedInsert', ''), false);
238 17
        $this->noData             = Util\Str::toBoolean(Util\Arr::getValue($conf, 'noData', ''), false);
239
        $this->filePerTable       = Util\Str::toBoolean(Util\Arr::getValue($conf, 'filePerTable', ''), false);
240
        $this->routines           = Util\Str::toBoolean(Util\Arr::getValue($conf, 'routines', ''), false);
241
        $this->skipTriggers       = Util\Str::toBoolean(Util\Arr::getValue($conf, 'skipTriggers', ''), false);
242
243
        // this doesn't fail, but it doesn't work, so throw an exception so the user understands
244
        if ($this->filePerTable && count($this->structureOnly)) {
245 18
            throw new Exception('\'structureOnly\' can not be used with the \'filePerTable\' option');
246
        }
247 18
    }
248 18
249 18
    /**
250 18
     * Get tables and databases to backup
251 18
     *
252
     * @param array $conf
253
     */
254
    protected function setupSourceData(array $conf)
255
    {
256
        $this->tables        = Util\Str::toList(Util\Arr::getValue($conf, 'tables', ''));
257
        $this->databases     = Util\Str::toList(Util\Arr::getValue($conf, 'databases', ''));
258
        $this->ignoreTables  = Util\Str::toList(Util\Arr::getValue($conf, 'ignoreTables', ''));
259
        $this->structureOnly = Util\Str::toList(Util\Arr::getValue($conf, 'structureOnly', ''));
260
    }
261
262 4
    /**
263
     * Execute the backup
264
     *
265 4
     * @see    \phpbu\App\Backup\Source
266 1
     * @param  \phpbu\App\Backup\Target $target
267 1
     * @param  \phpbu\App\Result        $result
268 1
     * @return \phpbu\App\Backup\Source\Status
269
     * @throws \phpbu\App\Exception
270
     */
271 4
    public function backup(Target $target, Result $result) : Status
272
    {
273 4
        // create the writable dump directory for tables files
274
        $dumpTarget = $this->getDumpTarget($target);
275 4
        if ($this->filePerTable && !is_dir($dumpTarget)) {
276 1
            $fileMode = 0777;
277
278
            if (PHP_OS !== 'WINNT') {
279 3
                // get the permissions of the first directory that exists
280
                $pathSegments = explode(DIRECTORY_SEPARATOR, $dumpTarget);
281
282
                do {
283
                    $filename = implode(DIRECTORY_SEPARATOR, $pathSegments);
284
285
                    if (is_dir($filename) === false) {
286
                        continue;
287
                    }
288
289 2
                    $fileMode = substr(fileperms($filename), -4);
290
                    break;
291 2
                } while (array_pop($pathSegments) !== null);
292
            }
293 2
294 1
            $old = umask(0);
295 1
            mkdir($dumpTarget, $fileMode, true);
0 ignored issues
show
Bug introduced by
It seems like $fileMode can also be of type string; however, parameter $mode of mkdir() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

295
            mkdir($dumpTarget, /** @scrutinizer ignore-type */ $fileMode, true);
Loading history...
296 1
            umask($old);
297
        }
298 1
299 1
        $mysqldump = $this->execute($target);
300
301 1
        $result->debug($this->getExecutable($target)->getCommandPrintable());
302 1
303 1
        if (!$mysqldump->isSuccessful()) {
304 1
            throw new Exception('mysqldump failed:' . $mysqldump->getStdErr());
305
        }
306 1
307 1
        return $this->createStatus($target);
308 1
    }
309
310 1
    /**
311 1
     * Restore the backup
312
     *
313
     * @param  \phpbu\App\Backup\Target       $target
314 2
     * @param  \phpbu\App\Backup\Restore\Plan $plan
315
     * @return \phpbu\App\Backup\Source\Status
316
     */
317
    public function restore(Target $target, Plan $plan): Status
318
    {
319
        $executable = $this->createMysqlExecutable();
320
321
        if ($this->filePerTable) {
322
            $database    = $this->databases[0];
323 15
            $sourceTar   = $target->getPathname(true) . '.tar';
324
            $mysqlimport = $this->createMysqlimportExecutable('<table-file>', $database);
325 15
326 15
            $executable->useDatabase($database);
327 15
            $executable->useSourceFile('<table-file>');
328 15
329 15
            $mysqlCommand  = $executable->getCommandPrintable();
330 15
            $importCommand = $mysqlimport->getCommandPrintable();
331 15
            $mysqlComment  = 'Restore the structure, execute this for every table file';
332 15
            $importComment = 'Restore the data, execute this for every table file';
333 15
334 15
            $plan->addRestoreCommand('tar -xvf ' . $sourceTar, 'Extract the table files');
335 15
            $plan->addRestoreCommand($mysqlCommand, $mysqlComment);
336 15
            $plan->addRestoreCommand($importCommand, $importComment);
337 15
        } else {
338 15
            $executable->useSourceFile($target->getFilename(true));
339 15
            $plan->addRestoreCommand($executable->getCommandPrintable());
340 15
        }
341 15
342 15
        return Status::create()->uncompressedFile($target->getPathname());
343 15
    }
344 15
345 15
    /**
346
     * Create the Executable to run the mysqldump command.
347 15
     *
348 2
     * @param  \phpbu\App\Backup\Target $target
349
     * @return \phpbu\App\Cli\Executable
350 15
     */
351
    protected function createExecutable(Target $target) : Executable
352
    {
353
        $executable = new Executable\Mysqldump($this->pathToMysqldump);
354
        $executable->credentials($this->user, $this->password)
355
                   ->useHost($this->host)
356
                   ->usePort($this->port)
357
                   ->useProtocol($this->protocol)
358
                   ->useQuickMode($this->quick)
359 4
                   ->lockTables($this->lockTables)
360
                   ->dumpBlobsHexadecimal($this->hexBlob)
361
                   ->addGTIDStatement($this->gtidPurged)
362 4
                   ->useSslCa($this->sslCa)
363 1
                   ->useCompression($this->compress)
364
                   ->skipExtendedInsert($this->skipExtendedInsert)
365
                   ->dumpTables($this->tables)
366
                   ->singleTransaction($this->singleTransaction)
367
                   ->dumpDatabases($this->databases)
368 3
                   ->ignoreTables($this->ignoreTables)
369 1
                   ->produceFilePerTable($this->filePerTable)
370
                   ->dumpNoData($this->noData)
371
                   ->dumpRoutines($this->routines)
372
                   ->skipTriggers($this->skipTriggers)
373 2
                   ->dumpStructureOnly($this->structureOnly)
374
                   ->dumpTo($this->getDumpTarget($target));
375
        // if compression is active and commands can be piped
376
        if ($this->isHandlingCompression($target)) {
377
            $executable->compressOutput($target->getCompression());
378
        }
379
        return $executable;
380
    }
381
382 15
    /**
383
     * Create backup status.
384 15
     *
385
     * @param  \phpbu\App\Backup\Target $target
386
     * @return \phpbu\App\Backup\Source\Status
387
     */
388
    protected function createStatus(Target $target) : Status
389
    {
390
        // file_per_table creates a directory with all the files
391
        if ($this->filePerTable) {
392
            return Status::create()->uncompressedDirectory($this->getDumpTarget($target));
393 15
        }
394
395 15
        // if compression is active and commands can be piped
396
        // compression is handled via pipe
397
        if ($this->isHandlingCompression($target)) {
398
            return Status::create();
399
        }
400
401
        // default create uncompressed dump file
402
        return Status::create()->uncompressedFile($this->getDumpTarget($target));
403 2
    }
404
405 2
    /**
406 2
     * Can compression be handled via pipe operator.
407 2
     *
408 2
     * @param  \phpbu\App\Backup\Target $target
409 2
     * @return bool
410 2
     */
411 2
    private function isHandlingCompression(Target $target) : bool
412
    {
413 2
        return $target->shouldBeCompressed() && Util\Cli::canPipe() && $target->getCompression()->isPipeable();
414
    }
415
416
    /**
417
     * Return dump target path.
418
     *
419
     * @param  \phpbu\App\Backup\Target $target
420
     * @return string
421
     */
422
    private function getDumpTarget(Target $target) : string
423
    {
424 1
        return $target->getPathnamePlain() . ($this->filePerTable ? '.dump' : '');
425
    }
426 1
427 1
    /**
428 1
     * Create the Executable to run the mysql command.
429 1
     *
430 1
     * @return \phpbu\App\Cli\Executable\Mysql
431 1
     */
432 1
    private function createMysqlExecutable(): Executable\Mysql
433
    {
434 1
        $executable = new Executable\Mysql($this->pathToMysql);
435
        $executable->credentials($this->user, $this->password)
436
            ->useHost($this->host)
437
            ->usePort($this->port)
438
            ->useProtocol($this->protocol)
439
            ->useQuickMode($this->quick)
440
            ->useCompression($this->compress);
441
442
        return $executable;
443
    }
444
445
    /**
446
     * Create the Executable to run the mysqlimport command.
447
     *
448
     * @param string $sourceFilename
449
     * @param string $targetDatabase
450
     *
451
     * @return \phpbu\App\Cli\Executable\Mysqlimport
452
     */
453
    private function createMysqlimportExecutable(string $sourceFilename, string $targetDatabase): Executable\Mysqlimport
454
    {
455
        $executable = new Executable\Mysqlimport($this->pathToMysqlimport);
456
        $executable->setSourceAndTarget($sourceFilename, $targetDatabase)
457
            ->credentials($this->user, $this->password)
458
            ->useHost($this->host)
459
            ->usePort($this->port)
460
            ->useProtocol($this->protocol)
461
            ->useCompression($this->compress);
462
463
        return $executable;
464
    }
465
}
466