Completed
Push — master ( 065ca4...7133c6 )
by Christian
03:24
created

DumpCommand::createExecsArray()   F

Complexity

Conditions 12
Paths 1152

Size

Total Lines 67
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 40
nc 1152
nop 2
dl 0
loc 67
rs 2.5741
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace N98\Magento\Command\Database;
4
5
use InvalidArgumentException;
6
use N98\Magento\Command\Database\Compressor\Compressor;
7
use N98\Util\Console\Enabler;
8
use N98\Util\Console\Helper\DatabaseHelper;
9
use N98\Util\Exec;
10
use N98\Util\VerifyOrDie;
11
use Symfony\Component\Console\Helper\DialogHelper;
12
use Symfony\Component\Console\Input\InputArgument;
13
use Symfony\Component\Console\Input\InputInterface;
14
use Symfony\Component\Console\Input\InputOption;
15
use Symfony\Component\Console\Output\OutputInterface;
16
17
class DumpCommand extends AbstractDatabaseCommand
18
{
19
    /**
20
     * @var array
21
     */
22
    protected $tableDefinitions = null;
23
24
    /**
25
     * @var array
26
     */
27
    protected $commandConfig = null;
28
29
    protected function configure()
30
    {
31
        $this
32
            ->setName('db:dump')
33
            ->addArgument('filename', InputArgument::OPTIONAL, 'Dump filename')
34
            ->addOption(
35
                'add-time',
36
                't',
37
                InputOption::VALUE_OPTIONAL,
38
                'Append or prepend a timestamp to filename if a filename is provided. ' .
39
                'Possible values are "suffix", "prefix" or "no".'
40
            )
41
            ->addOption(
42
                'compression',
43
                'c',
44
                InputOption::VALUE_REQUIRED,
45
                'Compress the dump file using one of the supported algorithms'
46
            )
47
            ->addOption(
48
                'dump-option',
49
                null,
50
                InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
51
                'Option(s) to pass to mysqldump command. E.g. --dump-option="--set-gtid-purged=off"'
52
            )
53
            ->addOption(
54
                'xml',
55
                null,
56
                InputOption::VALUE_NONE,
57
                'Dump database in xml format'
58
            )
59
            ->addOption(
60
                'hex-blob',
61
                null,
62
                InputOption::VALUE_NONE,
63
                'Dump binary columns using hexadecimal notation (for example, "abc" becomes 0x616263)'
64
            )
65
            ->addOption(
66
                'only-command',
67
                null,
68
                InputOption::VALUE_NONE,
69
                'Print only mysqldump command. Do not execute'
70
            )
71
            ->addOption(
72
                'print-only-filename',
73
                null,
74
                InputOption::VALUE_NONE,
75
                'Execute and prints no output except the dump filename'
76
            )
77
            ->addOption(
78
                'dry-run',
79
                null,
80
                InputOption::VALUE_NONE,
81
                'do everything but the dump'
82
            )
83
            ->addOption(
84
                'no-single-transaction',
85
                null,
86
                InputOption::VALUE_NONE,
87
                'Do not use single-transaction (not recommended, this is blocking)'
88
            )
89
            ->addOption(
90
                'human-readable',
91
                null,
92
                InputOption::VALUE_NONE,
93
                'Use a single insert with column names per row. Useful to track database differences. Use db:import ' .
94
                '--optimize for speeding up the import.'
95
            )
96
            ->addOption(
97
                'add-routines',
98
                null,
99
                InputOption::VALUE_NONE,
100
                'Include stored routines in dump (procedures & functions)'
101
            )
102
            ->addOption('stdout', null, InputOption::VALUE_NONE, 'Dump to stdout')
103
            ->addOption(
104
                'strip',
105
                's',
106
                InputOption::VALUE_OPTIONAL,
107
                'Tables to strip (dump only structure of those tables)'
108
            )
109
            ->addOption(
110
                'exclude',
111
                'e',
112
                InputOption::VALUE_OPTIONAL,
113
                'Tables to exclude from the dump'
114
            )
115
            ->addOption(
116
                'include',
117
                'i',
118
                InputOption::VALUE_OPTIONAL,
119
                'Tables to include in the dump'
120
            )
121
            ->addOption(
122
                'force',
123
                'f',
124
                InputOption::VALUE_NONE,
125
                'Do not prompt if all options are defined'
126
            )
127
            ->setDescription('Dumps database with mysqldump cli client');
128
129
        $help = <<<HELP
130
Dumps configured magento database with `mysqldump`. You must have installed
131
the MySQL client tools.
132
133
On debian systems run `apt-get install mysql-client` to do that.
134
135
The command reads app/etc/local.xml to find the correct settings.
136
137
See it in action: http://youtu.be/ttjZHY6vThs
138
139
- If you like to prepend a timestamp to the dump name the --add-time option
140
  can be used.
141
142
- The command comes with a compression function. Add i.e. `--compression=gz`
143
  to dump directly in gzip compressed file.
144
145
HELP;
146
        $this->setHelp($help);
147
    }
148
149
    /**
150
     * @return array
151
     *
152
     * @deprecated Use database helper
153
     */
154
    private function getTableDefinitions()
155
    {
156
        $this->commandConfig = $this->getCommandConfig();
157
158
        if (is_null($this->tableDefinitions)) {
159
            /* @var $dbHelper DatabaseHelper */
160
            $dbHelper = $this->getHelper('database');
161
162
            $this->tableDefinitions = $dbHelper->getTableDefinitions($this->commandConfig);
163
        }
164
165
        return $this->tableDefinitions;
166
    }
167
168
    /**
169
     * Generate help for table definitions
170
     *
171
     * @return string
172
     */
173
    public function getTableDefinitionHelp()
174
    {
175
        $messages = PHP_EOL;
176
        $this->commandConfig = $this->getCommandConfig();
177
        $messages .= <<<HELP
178
<comment>Strip option</comment>
179
 If you like to skip data of some tables you can use the --strip option.
180
 The strip option creates only the structure of the defined tables and
181
 forces `mysqldump` to skip the data.
182
183
 Separate each table to strip by a space.
184
 You can use wildcards like * and ? in the table names to strip multiple
185
 tables. In addition you can specify pre-defined table groups, that start
186
 with an
187
188
 Example: "dataflow_batch_export unimportant_module_* @log
189
190
    $ n98-magerun.phar db:dump --strip="@stripped"
191
192
<comment>Available Table Groups</comment>
193
194
HELP;
195
196
        $definitions = $this->getTableDefinitions();
0 ignored issues
show
Deprecated Code introduced by
The method N98\Magento\Command\Data...::getTableDefinitions() has been deprecated with message: Use database helper

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
197
        $list = array();
198
        $maxNameLen = 0;
199
        foreach ($definitions as $id => $definition) {
200
            $name = '@' . $id;
201
            $description = isset($definition['description']) ? $definition['description'] . '.' : '';
202
            $nameLen = strlen($name);
203
            if ($nameLen > $maxNameLen) {
204
                $maxNameLen = $nameLen;
205
            }
206
            $list[] = array($name, $description);
207
        }
208
209
        $decrSize = 78 - $maxNameLen - 3;
210
211
        foreach ($list as $entry) {
212
            list($name, $description) = $entry;
213
            $delta = max(0, $maxNameLen - strlen($name));
214
            $spacer = $delta ? str_repeat(' ', $delta) : '';
215
            $buffer = wordwrap($description, $decrSize);
216
            $buffer = strtr($buffer, array("\n" => "\n" . str_repeat(' ', 3 + $maxNameLen)));
217
            $messages .= sprintf(" <info>%s</info>%s  %s\n", $name, $spacer, $buffer);
218
        }
219
220
        $messages .= <<<HELP
221
222
Extended: https://github.com/netz98/n98-magerun/wiki/Stripped-Database-Dumps
223
HELP;
224
225
        return $messages;
226
    }
227
228
    public function getHelp()
229
    {
230
        return
231
            parent::getHelp() . PHP_EOL
232
            . $this->getCompressionHelp() . PHP_EOL
233
            . $this->getTableDefinitionHelp();
234
    }
235
236
    /**
237
     * @param InputInterface $input
238
     * @param OutputInterface $output
239
     *
240
     * @return int|void
241
     */
242
    protected function execute(InputInterface $input, OutputInterface $output)
243
    {
244
        // communicate early what is required for this command to run (is enabled)
245
        $enabler = new Enabler($this);
246
        $enabler->functionExists('exec');
247
        $enabler->functionExists('passthru');
248
        $enabler->operatingSystemIsNotWindows();
249
250
        // TODO(tk): Merge the DatabaseHelper, detectDbSettings is within abstract database command base class
251
        $this->detectDbSettings($output);
252
253
        if ($this->nonCommandOutput($input)) {
254
            $this->writeSection($output, 'Dump MySQL Database');
255
        }
256
257
        list($fileName, $execs) = $this->createExecsArray($input, $output);
258
259
        $this->runExecs($execs, $fileName, $input, $output);
260
    }
261
262
    /**
263
     * @param InputInterface $input
264
     * @param OutputInterface $output
265
     * @return array
266
     */
267
    private function createExecsArray(InputInterface $input, OutputInterface $output)
268
    {
269
        $execs = array();
270
271
        $dumpOptions = '';
272
        if (!$input->getOption('no-single-transaction')) {
273
            $dumpOptions .= '--single-transaction --quick ';
274
        }
275
276
        if ($input->getOption('human-readable')) {
277
            $dumpOptions .= '--complete-insert --skip-extended-insert ';
278
        }
279
280
        if ($input->getOption('add-routines')) {
281
            $dumpOptions .= '--routines ';
282
        }
283
284
        if ($input->getOption('xml')) {
285
            $dumpOptions .= '--xml ';
286
        }
287
288
        if ($input->getOption('hex-blob')) {
289
            $dumpOptions .= '--hex-blob ';
290
        }
291
292
        $options = $input->getOption('dump-option');
293
        if (count($options) > 0) {
294
            $dumpOptions .= implode(' ', $options) . ' ';
295
        }
296
297
        $compressor = $this->getCompressor($input->getOption('compression'));
0 ignored issues
show
Deprecated Code introduced by
The method N98\Magento\Command\Data...ommand::getCompressor() has been deprecated with message: Since 1.97.29; use AbstractCompressor::create() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
298
        $fileName = $this->getFileName($input, $output, $compressor);
299
300
        /* @var $database DatabaseHelper */
301
        $database = $this->getDatabaseHelper();
302
303
        $mysqlClientToolConnectionString = $database->getMysqlClientToolConnectionString();
304
305
        $stripTables = $this->stripTables($input, $output);
306
        if ($stripTables) {
307
            // dump structure for strip-tables
308
            $exec = 'mysqldump ' . $dumpOptions . '--no-data ' . $mysqlClientToolConnectionString;
309
            $exec .= ' ' . implode(' ', $stripTables);
310
            $exec .= $this->postDumpPipeCommands();
311
            $exec = $compressor->getCompressingCommand($exec);
312
            if (!$input->getOption('stdout')) {
313
                $exec .= ' > ' . escapeshellarg($fileName);
314
            }
315
            $execs[] = $exec;
316
        }
317
318
        $excludeTables = $this->excludeTables($input, $output);
319
320
        // dump data for all other tables
321
        $ignore = '';
322
        foreach (array_merge($excludeTables, $stripTables) as $ignoreTable) {
323
            $ignore .= '--ignore-table=' . $this->dbSettings['dbname'] . '.' . $ignoreTable . ' ';
324
        }
325
        $exec = 'mysqldump ' . $dumpOptions . $mysqlClientToolConnectionString . ' ' . $ignore;
326
        $exec .= $this->postDumpPipeCommands();
327
        $exec = $compressor->getCompressingCommand($exec);
328
        if (!$input->getOption('stdout')) {
329
            $exec .= (count($stripTables) > 0 ? ' >> ' : ' > ') . escapeshellarg($fileName);
330
        }
331
        $execs[] = $exec;
332
        return array($fileName, $execs);
333
    }
334
335
    /**
336
     * @param array $execs
337
     * @param string $fileName
338
     * @param InputInterface $input
339
     * @param OutputInterface $output
340
     */
341
    private function runExecs(array $execs, $fileName, InputInterface $input, OutputInterface $output)
342
    {
343
        if ($input->getOption('only-command') && !$input->getOption('print-only-filename')) {
344
            foreach ($execs as $command) {
345
                $output->writeln($command);
346
            }
347
        } else {
348
            if ($this->nonCommandOutput($input)) {
349
                $output->writeln(
350
                    '<comment>Start dumping database <info>' . $this->dbSettings['dbname'] .
351
                    '</info> to file <info>' . $fileName . '</info>'
352
                );
353
            }
354
355
            $commands = $input->getOption('dry-run') ? array() : $execs;
356
357
            foreach ($commands as $command) {
358
                if (!$this->runExec($command, $input, $output)) {
359
                    return;
360
                }
361
            }
362
363
            if (!$input->getOption('stdout') && !$input->getOption('print-only-filename')) {
364
                $output->writeln('<info>Finished</info>');
365
            }
366
        }
367
368
        if ($input->getOption('print-only-filename')) {
369
            $output->writeln($fileName);
370
        }
371
    }
372
373
    /**
374
     * @param string $command
375
     * @param InputInterface $input
376
     * @param OutputInterface $output
377
     * @return bool
378
     */
379
    private function runExec($command, InputInterface $input, OutputInterface $output)
380
    {
381
        $commandOutput = '';
382
383
        if ($input->getOption('stdout')) {
384
            passthru($command, $returnValue);
385
        } else {
386
            Exec::run($command, $commandOutput, $returnValue);
387
        }
388
389
        if ($returnValue > 0) {
390
            $output->writeln('<error>' . $commandOutput . '</error>');
391
            $output->writeln('<error>Return Code: ' . $returnValue . '. ABORTED.</error>');
392
393
            return false;
394
        }
395
396
        return true;
397
    }
398
399
    /**
400
     * @param InputInterface $input
401
     * @param OutputInterface $output
402
     * @return array
403
     */
404
    private function stripTables(InputInterface $input, OutputInterface $output)
405
    {
406
        if (!$input->getOption('strip')) {
407
            return array();
408
        }
409
410
        $stripTables = $this->resolveDatabaseTables($input->getOption('strip'));
411
412
        if ($this->nonCommandOutput($input)) {
413
            $output->writeln(
414
                sprintf('<comment>No-data export for: <info>%s</info></comment>', implode(' ', $stripTables))
415
            );
416
        }
417
418
        return $stripTables;
419
    }
420
421
    /**
422
     * @param InputInterface $input
423
     * @param OutputInterface $output
424
     * @return array
425
     */
426
    private function excludeTables(InputInterface $input, OutputInterface $output)
427
    {
428
        if ($input->getOption('exclude') && $input->getOption('include')) {
429
            throw new InvalidArgumentException('Cannot specify --include with --exclude');
430
        }
431
432
        if (!$input->getOption('exclude')) {
433
            $excludeTables = array();
434
        } else {
435
            $excludeTables = $this->resolveDatabaseTables($input->getOption('exclude'));
436
437
            if ($this->nonCommandOutput($input)) {
438
                $output->writeln(
439
                    sprintf('<comment>Excluded: <info>%s</info></comment>', implode(' ', $excludeTables))
440
                );
441
            }
442
        }
443
444
        if ($input->getOption('include')) {
445
            $includeTables = $this->resolveDatabaseTables($input->getOption('include'));
446
            $excludeTables = array_diff($this->getDatabaseHelper()->getTables(), $includeTables);
447
            if ($this->nonCommandOutput($input)) {
448
                $output->writeln(
449
                    sprintf('<comment>Included: <info>%s</info></comment>', implode(' ', $includeTables))
450
                );
451
            }
452
        }
453
454
        return $excludeTables;
455
    }
456
457
    /**
458
     * @param string $list space separated list of tables
459
     * @return array
460
     */
461
    private function resolveDatabaseTables($list)
462
    {
463
        $database = $this->getDatabaseHelper();
464
465
        return $database->resolveTables(
466
            explode(' ', $list),
467
            $database->getTableDefinitions($this->getCommandConfig())
468
        );
469
    }
470
471
    /**
472
     * Commands which filter mysql data. Piped to mysqldump command
473
     *
474
     * @return string
475
     */
476
    protected function postDumpPipeCommands()
477
    {
478
        return ' | LANG=C LC_CTYPE=C LC_ALL=C sed -e ' . escapeshellarg('s/DEFINER[ ]*=[ ]*[^*]*\*/\*/');
479
    }
480
481
    /**
482
     * @param InputInterface $input
483
     * @param OutputInterface $output
484
     * @param Compressor $compressor
485
     *
486
     * @return string
487
     */
488
    protected function getFileName(InputInterface $input, OutputInterface $output, Compressor $compressor)
489
    {
490
        if ($input->getOption('xml')) {
491
            $nameExtension = '.xml';
492
        } else {
493
            $nameExtension = '.sql';
494
        }
495
496
        $optionAddTime = $input->getOption('add-time');
497
        list($namePrefix, $nameSuffix) = $this->getFileNamePrefixSuffix($optionAddTime);
498
499
        if (
500
            (
501
                ($fileName = $input->getArgument('filename')) === null
502
                || ($isDir = is_dir($fileName))
503
            )
504
            && !$input->getOption('stdout')
505
        ) {
506
            $defaultName = VerifyOrDie::filename(
507
                $namePrefix . $this->dbSettings['dbname'] . $nameSuffix . $nameExtension
508
            );
509
            if (isset($isDir) && $isDir) {
510
                $defaultName = rtrim($fileName, '/') . '/' . $defaultName;
511
            }
512
            if (!$input->getOption('force')) {
513
                /** @var DialogHelper $dialog */
514
                $dialog = $this->getHelper('dialog');
515
                $fileName = $dialog->ask(
516
                    $output,
517
                    '<question>Filename for SQL dump:</question> [<comment>' . $defaultName . '</comment>]',
518
                    $defaultName
519
                );
520
            } else {
521
                $fileName = $defaultName;
522
            }
523
        } else {
524
            if ($optionAddTime) {
525
                $pathParts = pathinfo($fileName);
526
                $fileName = ($pathParts['dirname'] == '.' ? '' : $pathParts['dirname'] . '/') .
527
                    $namePrefix . $pathParts['filename'] . $nameSuffix . '.' . $pathParts['extension'];
528
            }
529
        }
530
531
        $fileName = $compressor->getFileName($fileName);
532
533
        return $fileName;
534
    }
535
536
    /**
537
     * @param null|bool|string $optionAddTime [optional] true for default "suffix", other string values: "prefix", "no"
538
     * @return array
539
     */
540
    private function getFileNamePrefixSuffix($optionAddTime = null)
541
    {
542
        $namePrefix = '';
543
        $nameSuffix = '';
544
        if ($optionAddTime === null) {
545
            return array($namePrefix, $nameSuffix);
546
        }
547
548
        $timeStamp = date('Y-m-d_His');
549
550
        if (in_array($optionAddTime, array('suffix', true), true)) {
551
            $nameSuffix = '_' . $timeStamp;
552
        } elseif ($optionAddTime === 'prefix') {
553
            $namePrefix = $timeStamp . '_';
554
        } elseif ($optionAddTime !== 'no') {
555
            throw new InvalidArgumentException(
556
                sprintf(
557
                    'Invalid --add-time value %s, possible values are none (for) "suffix", "prefix" or "no"',
558
                    var_export($optionAddTime, true)
559
                )
560
            );
561
        }
562
563
        return array($namePrefix, $nameSuffix);
564
    }
565
566
    /**
567
     * @param InputInterface $input
568
     * @return bool
569
     */
570
    private function nonCommandOutput(InputInterface $input)
571
    {
572
        return
573
            !$input->getOption('stdout')
574
            && !$input->getOption('only-command')
575
            && !$input->getOption('print-only-filename');
576
    }
577
}
578