Completed
Push — master ( f5a8cf...dc6729 )
by Christian
07:18
created

DumpCommand::createExecsArray()   D

Complexity

Conditions 11
Paths 576

Size

Total Lines 62
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 37
nc 576
nop 2
dl 0
loc 62
rs 4.0246
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
                'xml',
49
                null,
50
                InputOption::VALUE_NONE,
51
                'Dump database in xml format'
52
            )
53
            ->addOption(
54
                'hex-blob',
55
                null,
56
                InputOption::VALUE_NONE,
57
                'Dump binary columns using hexadecimal notation (for example, "abc" becomes 0x616263)'
58
            )
59
            ->addOption(
60
                'only-command',
61
                null,
62
                InputOption::VALUE_NONE,
63
                'Print only mysqldump command. Do not execute'
64
            )
65
            ->addOption(
66
                'print-only-filename',
67
                null,
68
                InputOption::VALUE_NONE,
69
                'Execute and prints no output except the dump filename'
70
            )
71
            ->addOption(
72
                'dry-run',
73
                null,
74
                InputOption::VALUE_NONE,
75
                'do everything but the dump'
76
            )
77
            ->addOption(
78
                'no-single-transaction',
79
                null,
80
                InputOption::VALUE_NONE,
81
                'Do not use single-transaction (not recommended, this is blocking)'
82
            )
83
            ->addOption(
84
                'human-readable',
85
                null,
86
                InputOption::VALUE_NONE,
87
                'Use a single insert with column names per row. Useful to track database differences. Use db:import ' .
88
                '--optimize for speeding up the import.'
89
            )
90
            ->addOption(
91
                'add-routines',
92
                null,
93
                InputOption::VALUE_NONE,
94
                'Include stored routines in dump (procedures & functions)'
95
            )
96
            ->addOption('stdout', null, InputOption::VALUE_NONE, 'Dump to stdout')
97
            ->addOption(
98
                'strip',
99
                's',
100
                InputOption::VALUE_OPTIONAL,
101
                'Tables to strip (dump only structure of those tables)'
102
            )
103
            ->addOption(
104
                'exclude',
105
                'e',
106
                InputOption::VALUE_OPTIONAL,
107
                'Tables to exclude from the dump'
108
            )
109
            ->addOption(
110
                'include',
111
                'i',
112
                InputOption::VALUE_OPTIONAL,
113
                'Tables to include in the dump'
114
            )
115
            ->addOption(
116
                'force',
117
                'f',
118
                InputOption::VALUE_NONE,
119
                'Do not prompt if all options are defined'
120
            )
121
            ->setDescription('Dumps database with mysqldump cli client');
122
123
        $help = <<<HELP
124
Dumps configured magento database with `mysqldump`. You must have installed
125
the MySQL client tools.
126
127
On debian systems run `apt-get install mysql-client` to do that.
128
129
The command reads app/etc/local.xml to find the correct settings.
130
131
See it in action: http://youtu.be/ttjZHY6vThs
132
133
- If you like to prepend a timestamp to the dump name the --add-time option
134
  can be used.
135
136
- The command comes with a compression function. Add i.e. `--compression=gz`
137
  to dump directly in gzip compressed file.
138
139
HELP;
140
        $this->setHelp($help);
141
    }
142
143
    /**
144
     * @return array
145
     *
146
     * @deprecated Use database helper
147
     */
148
    private function getTableDefinitions()
149
    {
150
        $this->commandConfig = $this->getCommandConfig();
151
152
        if (is_null($this->tableDefinitions)) {
153
            /* @var $dbHelper DatabaseHelper */
154
            $dbHelper = $this->getHelper('database');
155
156
            $this->tableDefinitions = $dbHelper->getTableDefinitions($this->commandConfig);
157
        }
158
159
        return $this->tableDefinitions;
160
    }
161
162
    /**
163
     * Generate help for table definitions
164
     *
165
     * @return string
166
     */
167
    public function getTableDefinitionHelp()
168
    {
169
        $messages = PHP_EOL;
170
        $this->commandConfig = $this->getCommandConfig();
171
        $messages .= <<<HELP
172
<comment>Strip option</comment>
173
 If you like to skip data of some tables you can use the --strip option.
174
 The strip option creates only the structure of the defined tables and
175
 forces `mysqldump` to skip the data.
176
177
 Separate each table to strip by a space.
178
 You can use wildcards like * and ? in the table names to strip multiple
179
 tables. In addition you can specify pre-defined table groups, that start
180
 with an
181
182
 Example: "dataflow_batch_export unimportant_module_* @log
183
184
    $ n98-magerun.phar db:dump --strip="@stripped"
185
186
<comment>Available Table Groups</comment>
187
188
HELP;
189
190
        $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...
191
        $list = array();
192
        $maxNameLen = 0;
193
        foreach ($definitions as $id => $definition) {
194
            $name = '@' . $id;
195
            $description = isset($definition['description']) ? $definition['description'] . '.' : '';
196
            $nameLen = strlen($name);
197
            if ($nameLen > $maxNameLen) {
198
                $maxNameLen = $nameLen;
199
            }
200
            $list[] = array($name, $description);
201
        }
202
203
        $decrSize = 78 - $maxNameLen - 3;
204
205
        foreach ($list as $entry) {
206
            list($name, $description) = $entry;
207
            $delta = max(0, $maxNameLen - strlen($name));
208
            $spacer = $delta ? str_repeat(' ', $delta) : '';
209
            $buffer = wordwrap($description, $decrSize);
210
            $buffer = strtr($buffer, array("\n" => "\n" . str_repeat(' ', 3 + $maxNameLen)));
211
            $messages .= sprintf(" <info>%s</info>%s  %s\n", $name, $spacer, $buffer);
212
        }
213
214
        $messages .= <<<HELP
215
216
Extended: https://github.com/netz98/n98-magerun/wiki/Stripped-Database-Dumps
217
HELP;
218
219
        return $messages;
220
    }
221
222
    public function getHelp()
223
    {
224
        return
225
            parent::getHelp() . PHP_EOL
226
            . $this->getCompressionHelp() . PHP_EOL
227
            . $this->getTableDefinitionHelp();
228
    }
229
230
    /**
231
     * @param InputInterface $input
232
     * @param OutputInterface $output
233
     *
234
     * @return int|void
235
     */
236
    protected function execute(InputInterface $input, OutputInterface $output)
237
    {
238
        // communicate early what is required for this command to run (is enabled)
239
        $enabler = new Enabler($this);
240
        $enabler->functionExists('exec');
241
        $enabler->functionExists('passthru');
242
        $enabler->operatingSystemIsNotWindows();
243
244
        // TODO(tk): Merge the DatabaseHelper, detectDbSettings is within abstract database command base class
245
        $this->detectDbSettings($output);
246
247
        if ($this->nonCommandOutput($input)) {
248
            $this->writeSection($output, 'Dump MySQL Database');
249
        }
250
251
        list($fileName, $execs) = $this->createExecsArray($input, $output);
252
253
        $this->runExecs($execs, $fileName, $input, $output);
254
    }
255
256
    /**
257
     * @param InputInterface $input
258
     * @param OutputInterface $output
259
     * @return array
260
     */
261
    private function createExecsArray(InputInterface $input, OutputInterface $output)
262
    {
263
        $execs = array();
264
265
        $dumpOptions = '';
266
        if (!$input->getOption('no-single-transaction')) {
267
            $dumpOptions .= '--single-transaction --quick ';
268
        }
269
270
        if ($input->getOption('human-readable')) {
271
            $dumpOptions .= '--complete-insert --skip-extended-insert ';
272
        }
273
274
        if ($input->getOption('add-routines')) {
275
            $dumpOptions .= '--routines ';
276
        }
277
278
        if ($input->getOption('xml')) {
279
            $dumpOptions .= '--xml ';
280
        }
281
282
        if ($input->getOption('hex-blob')) {
283
            $dumpOptions .= '--hex-blob ';
284
        }
285
286
        $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...
287
        $fileName = $this->getFileName($input, $output, $compressor);
288
289
        /* @var $database DatabaseHelper */
290
        $database = $this->getDatabaseHelper();
291
292
        $mysqlClientToolConnectionString = $database->getMysqlClientToolConnectionString();
293
294
        $stripTables = $this->stripTables($input, $output);
295
        if ($stripTables) {
296
            // dump structure for strip-tables
297
            $exec = 'mysqldump ' . $dumpOptions . '--no-data ' . $mysqlClientToolConnectionString;
298
            $exec .= ' ' . implode(' ', $stripTables);
299
            $exec .= $this->postDumpPipeCommands();
300
            $exec = $compressor->getCompressingCommand($exec);
301
            if (!$input->getOption('stdout')) {
302
                $exec .= ' > ' . escapeshellarg($fileName);
303
            }
304
            $execs[] = $exec;
305
        }
306
307
        $excludeTables = $this->excludeTables($input, $output);
308
309
        // dump data for all other tables
310
        $ignore = '';
311
        foreach (array_merge($excludeTables, $stripTables) as $ignoreTable) {
312
            $ignore .= '--ignore-table=' . $this->dbSettings['dbname'] . '.' . $ignoreTable . ' ';
313
        }
314
        $exec = 'mysqldump ' . $dumpOptions . $mysqlClientToolConnectionString . ' ' . $ignore;
315
        $exec .= $this->postDumpPipeCommands();
316
        $exec = $compressor->getCompressingCommand($exec);
317
        if (!$input->getOption('stdout')) {
318
            $exec .= (count($stripTables) > 0 ? ' >> ' : ' > ') . escapeshellarg($fileName);
319
        }
320
        $execs[] = $exec;
321
        return array($fileName, $execs);
322
    }
323
324
    /**
325
     * @param array $execs
326
     * @param string $fileName
327
     * @param InputInterface $input
328
     * @param OutputInterface $output
329
     */
330
    private function runExecs(array $execs, $fileName, InputInterface $input, OutputInterface $output)
331
    {
332
        if ($input->getOption('only-command') && !$input->getOption('print-only-filename')) {
333
            foreach ($execs as $command) {
334
                $output->writeln($command);
335
            }
336
        } else {
337
            if ($this->nonCommandOutput($input)) {
338
                $output->writeln(
339
                    '<comment>Start dumping database <info>' . $this->dbSettings['dbname'] .
340
                    '</info> to file <info>' . $fileName . '</info>'
341
                );
342
            }
343
344
            $commands = $input->getOption('dry-run') ? array() : $execs;
345
346
            foreach ($commands as $command) {
347
                if (!$this->runExec($command, $input, $output)) {
348
                    return;
349
                }
350
            }
351
352
            if (!$input->getOption('stdout') && !$input->getOption('print-only-filename')) {
353
                $output->writeln('<info>Finished</info>');
354
            }
355
        }
356
357
        if ($input->getOption('print-only-filename')) {
358
            $output->writeln($fileName);
359
        }
360
    }
361
362
    /**
363
     * @param string $command
364
     * @param InputInterface $input
365
     * @param OutputInterface $output
366
     * @return bool
367
     */
368
    private function runExec($command, InputInterface $input, OutputInterface $output)
369
    {
370
        $commandOutput = '';
371
372
        if ($input->getOption('stdout')) {
373
            passthru($command, $returnValue);
374
        } else {
375
            Exec::run($command, $commandOutput, $returnValue);
376
        }
377
378
        if ($returnValue > 0) {
379
            $output->writeln('<error>' . $commandOutput . '</error>');
380
            $output->writeln('<error>Return Code: ' . $returnValue . '. ABORTED.</error>');
381
382
            return false;
383
        }
384
385
        return true;
386
    }
387
388
    /**
389
     * @param InputInterface $input
390
     * @param OutputInterface $output
391
     * @return array
392
     */
393
    private function stripTables(InputInterface $input, OutputInterface $output)
394
    {
395
        if (!$input->getOption('strip')) {
396
            return array();
397
        }
398
399
        $stripTables = $this->resolveDatabaseTables($input->getOption('strip'));
400
401
        if ($this->nonCommandOutput($input)) {
402
            $output->writeln(
403
                sprintf('<comment>No-data export for: <info>%s</info></comment>', implode(' ', $stripTables))
404
            );
405
        }
406
407
        return $stripTables;
408
    }
409
410
    /**
411
     * @param InputInterface $input
412
     * @param OutputInterface $output
413
     * @return array
414
     */
415
    private function excludeTables(InputInterface $input, OutputInterface $output)
416
    {
417
        if ($input->getOption('exclude') && $input->getOption('include')) {
418
            throw new InvalidArgumentException('Cannot specify --include with --exclude');
419
        }
420
421
        if (!$input->getOption('exclude')) {
422
            $excludeTables = array();
423
        } else {
424
            $excludeTables = $this->resolveDatabaseTables($input->getOption('exclude'));
425
426
            if ($this->nonCommandOutput($input)) {
427
                $output->writeln(
428
                    sprintf('<comment>Excluded: <info>%s</info></comment>', implode(' ', $excludeTables))
429
                );
430
            }
431
        }
432
433
        if ($input->getOption('include')) {
434
            $includeTables = $this->resolveDatabaseTables($input->getOption('include'));
435
            $excludeTables = array_diff($this->getDatabaseHelper()->getTables(), $includeTables);
436
            if ($this->nonCommandOutput($input)) {
437
                $output->writeln(
438
                    sprintf('<comment>Included: <info>%s</info></comment>', implode(' ', $includeTables))
439
                );
440
            }
441
        }
442
443
        return $excludeTables;
444
    }
445
446
    /**
447
     * @param string $list space separated list of tables
448
     * @return array
449
     */
450
    private function resolveDatabaseTables($list)
451
    {
452
        $database = $this->getDatabaseHelper();
453
454
        return $database->resolveTables(
455
            explode(' ', $list),
456
            $database->getTableDefinitions($this->getCommandConfig())
457
        );
458
    }
459
460
    /**
461
     * Commands which filter mysql data. Piped to mysqldump command
462
     *
463
     * @return string
464
     */
465
    protected function postDumpPipeCommands()
466
    {
467
        return ' | LANG=C LC_CTYPE=C LC_ALL=C sed -e ' . escapeshellarg('s/DEFINER[ ]*=[ ]*[^*]*\*/\*/');
468
    }
469
470
    /**
471
     * @param InputInterface $input
472
     * @param OutputInterface $output
473
     * @param Compressor $compressor
474
     *
475
     * @return string
476
     */
477
    protected function getFileName(InputInterface $input, OutputInterface $output, Compressor $compressor)
478
    {
479
        if ($input->getOption('xml')) {
480
            $nameExtension = '.xml';
481
        } else {
482
            $nameExtension = '.sql';
483
        }
484
485
        $optionAddTime = $input->getOption('add-time');
486
        list($namePrefix, $nameSuffix) = $this->getFileNamePrefixSuffix($optionAddTime);
487
488
        if (
489
            (
490
                ($fileName = $input->getArgument('filename')) === null
491
                || ($isDir = is_dir($fileName))
492
            )
493
            && !$input->getOption('stdout')
494
        ) {
495
            $defaultName = VerifyOrDie::filename(
496
                $namePrefix . $this->dbSettings['dbname'] . $nameSuffix . $nameExtension
497
            );
498
            if (isset($isDir) && $isDir) {
499
                $defaultName = rtrim($fileName, '/') . '/' . $defaultName;
500
            }
501
            if (!$input->getOption('force')) {
502
                /** @var DialogHelper $dialog */
503
                $dialog = $this->getHelper('dialog');
504
                $fileName = $dialog->ask(
505
                    $output,
506
                    '<question>Filename for SQL dump:</question> [<comment>' . $defaultName . '</comment>]',
507
                    $defaultName
508
                );
509
            } else {
510
                $fileName = $defaultName;
511
            }
512
        } else {
513
            if ($optionAddTime) {
514
                $pathParts = pathinfo($fileName);
515
                $fileName = ($pathParts['dirname'] == '.' ? '' : $pathParts['dirname'] . '/') .
516
                    $namePrefix . $pathParts['filename'] . $nameSuffix . '.' . $pathParts['extension'];
517
            }
518
        }
519
520
        $fileName = $compressor->getFileName($fileName);
521
522
        return $fileName;
523
    }
524
525
    /**
526
     * @param null|bool|string $optionAddTime [optional] true for default "suffix", other string values: "prefix", "no"
527
     * @return array
528
     */
529
    private function getFileNamePrefixSuffix($optionAddTime = null)
530
    {
531
        $namePrefix = '';
532
        $nameSuffix = '';
533
        if ($optionAddTime === null) {
534
            return array($namePrefix, $nameSuffix);
535
        }
536
537
        $timeStamp = date('Y-m-d_His');
538
539
        if (in_array($optionAddTime, array('suffix', true), true)) {
540
            $nameSuffix = '_' . $timeStamp;
541
        } elseif ($optionAddTime === 'prefix') {
542
            $namePrefix = $timeStamp . '_';
543
        } elseif ($optionAddTime !== 'no') {
544
            throw new InvalidArgumentException(
545
                sprintf(
546
                    'Invalid --add-time value %s, possible values are none (for) "suffix", "prefix" or "no"',
547
                    var_export($optionAddTime, true)
548
                )
549
            );
550
        }
551
552
        return array($namePrefix, $nameSuffix);
553
    }
554
555
    /**
556
     * @param InputInterface $input
557
     * @return bool
558
     */
559
    private function nonCommandOutput(InputInterface $input)
560
    {
561
        return
562
            !$input->getOption('stdout')
563
            && !$input->getOption('only-command')
564
            && !$input->getOption('print-only-filename');
565
    }
566
}
567