Completed
Push — master ( 2e0516...6d6735 )
by Christian
19:18 queued 14:58
created

DumpCommand::getFileName()   C

Complexity

Conditions 12
Paths 21

Size

Total Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 50
rs 6.9666
c 0
b 0
f 0
cc 12
nc 21
nop 3

How to fix   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
        parent::configure();
32
        $this
33
            ->setName('db:dump')
34
            ->addArgument('filename', InputArgument::OPTIONAL, 'Dump filename')
35
            ->addOption(
36
                'add-time',
37
                't',
38
                InputOption::VALUE_OPTIONAL,
39
                'Append or prepend a timestamp to filename if a filename is provided. ' .
40
                'Possible values are "suffix", "prefix" or "no".',
41
                ''
42
            )
43
            ->addOption(
44
                'compression',
45
                'c',
46
                InputOption::VALUE_REQUIRED,
47
                'Compress the dump file using one of the supported algorithms'
48
            )
49
            ->addOption(
50
                'only-command',
51
                null,
52
                InputOption::VALUE_NONE,
53
                'Print only mysqldump command. Do not execute'
54
            )
55
            ->addOption(
56
                'print-only-filename',
57
                null,
58
                InputOption::VALUE_NONE,
59
                'Execute and prints no output except the dump filename'
60
            )
61
            ->addOption(
62
                'dry-run',
63
                null,
64
                InputOption::VALUE_NONE,
65
                'Do everything but the actual dump'
66
            )
67
            ->addOption(
68
                'no-single-transaction',
69
                null,
70
                InputOption::VALUE_NONE,
71
                'Do not use single-transaction (not recommended, this is blocking)'
72
            )
73
            ->addOption(
74
                'human-readable',
75
                null,
76
                InputOption::VALUE_NONE,
77
                'Use a single insert with column names per row. Useful to track database differences. Use db:import ' .
78
                '--optimize for speeding up the import.'
79
            )
80
            ->addOption(
81
                'git-friendly',
82
                null,
83
                InputOption::VALUE_NONE,
84
                'Use one insert statement, but with line breaks instead of separate insert statements. Similar to --human-readable, but you wont need to use --optimize to speed up the import.'
85
            )
86
            ->addOption(
87
                'add-routines',
88
                null,
89
                InputOption::VALUE_NONE,
90
                'Include stored routines in dump (procedures & functions)'
91
            )
92
            ->addOption('stdout',
93
                null,
94
                InputOption::VALUE_NONE,
95
                'Dump to stdout'
96
            )
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 entirely from the dump (including structure)'
108
            )
109
            ->addOption(
110
                'force',
111
                'f',
112
                InputOption::VALUE_NONE,
113
                'Do not prompt if all options are defined'
114
            )
115
            ->setDescription('Dumps database with mysqldump cli client');
116
117
        $help = <<<HELP
118
Dumps configured magento database with `mysqldump`. You must have installed
119
the MySQL client tools.
120
121
On debian systems run `apt-get install mysql-client` to do that.
122
123
The command reads app/etc/env.php to find the correct settings.
124
125
See it in action: http://youtu.be/ttjZHY6vThs
126
127
- If you like to prepend a timestamp to the dump name the --add-time option
128
  can be used.
129
130
- The command comes with a compression function. Add i.e. `--compression=gz`
131
  to dump directly in gzip compressed file.
132
133
HELP;
134
        $this->setHelp($help);
135
    }
136
137
    /**
138
     * @return array
139
     *
140
     * @deprecated Use database helper
141
     */
142
    private function getTableDefinitions()
143
    {
144
        $this->commandConfig = $this->getCommandConfig();
145
146
        if (is_null($this->tableDefinitions)) {
147
            /* @var $dbHelper DatabaseHelper */
148
            $dbHelper = $this->getHelper('database');
149
150
            $this->tableDefinitions = $dbHelper->getTableDefinitions($this->commandConfig);
151
        }
152
153
        return $this->tableDefinitions;
154
    }
155
156
    /**
157
     * Generate help for table definitions
158
     *
159
     * @return string
160
     */
161
    public function getTableDefinitionHelp()
162
    {
163
        $messages = PHP_EOL;
164
        $this->commandConfig = $this->getCommandConfig();
165
        $messages .= <<<HELP
166
<comment>Strip option</comment>
167
 If you like to skip data of some tables you can use the --strip option.
168
 The strip option creates only the structure of the defined tables and
169
 forces `mysqldump` to skip the data.
170
171
 Separate each table to strip by a space.
172
 You can use wildcards like * and ? in the table names to strip multiple
173
 tables. In addition you can specify pre-defined table groups, that start
174
 with an
175
176
 Example: "dataflow_batch_export unimportant_module_* @log
177
178
    $ n98-magerun2.phar db:dump --strip="@stripped"
179
180
<comment>Available Table Groups</comment>
181
182
HELP;
183
184
        $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...
185
        $list = array();
186
        $maxNameLen = 0;
187
        foreach ($definitions as $id => $definition) {
188
            $name = '@' . $id;
189
            $description = isset($definition['description']) ? $definition['description'] . '.' : '';
190
            $nameLen = strlen($name);
191
            if ($nameLen > $maxNameLen) {
192
                $maxNameLen = $nameLen;
193
            }
194
            $list[] = array($name, $description);
195
        }
196
197
        $decrSize = 78 - $maxNameLen - 3;
198
199
        foreach ($list as $entry) {
200
            list($name, $description) = $entry;
201
            $delta = max(0, $maxNameLen - strlen($name));
202
            $spacer = $delta ? str_repeat(' ', $delta) : '';
203
            $buffer = wordwrap($description, $decrSize);
204
            $buffer = strtr($buffer, array("\n" => "\n" . str_repeat(' ', 3 + $maxNameLen)));
205
            $messages .= sprintf(" <info>%s</info>%s  %s\n", $name, $spacer, $buffer);
206
        }
207
208
        $messages .= <<<HELP
209
210
Extended: https://github.com/netz98/n98-magerun/wiki/Stripped-Database-Dumps
211
HELP;
212
213
        return $messages;
214
    }
215
216
    public function getHelp()
217
    {
218
        return
219
            parent::getHelp() . PHP_EOL
220
            . $this->getCompressionHelp() . PHP_EOL
221
            . $this->getTableDefinitionHelp();
222
    }
223
224
    /**
225
     * @param InputInterface $input
226
     * @param OutputInterface $output
227
     *
228
     * @return int|void
229
     */
230
    protected function execute(InputInterface $input, OutputInterface $output)
231
    {
232
        // communicate early what is required for this command to run (is enabled)
233
        $enabler = new Enabler($this);
234
        $enabler->functionExists('exec');
235
        $enabler->functionExists('passthru');
236
        $enabler->operatingSystemIsNotWindows();
237
238
        $this->detectDbSettings($output);
239
240
        if ($this->nonCommandOutput($input)) {
241
            $this->writeSection($output, 'Dump MySQL Database');
242
        }
243
244
        $execs = $this->createExecs($input, $output);
245
246
        $this->runExecs($execs, $input, $output);
247
    }
248
249
    /**
250
     * @param InputInterface $input
251
     * @param OutputInterface $output
252
     * @return Execs
253
     */
254
    private function createExecs(InputInterface $input, OutputInterface $output)
255
    {
256
        $execs = new Execs('mysqldump');
257
        $execs->setCompression($input->getOption('compression'));
258
        $execs->setFileName($this->getFileName($input, $output, $execs->getCompressor()));
259
260
        if (!$input->getOption('no-single-transaction')) {
261
            $execs->addOptions('--single-transaction --quick');
262
        }
263
264
        if ($input->getOption('human-readable')) {
265
            $execs->addOptions('--complete-insert --skip-extended-insert ');
266
        }
267
268
        if ($input->getOption('add-routines')) {
269
            $execs->addOptions('--routines ');
270
        }
271
272
        $postDumpGitFriendlyPipeCommands = '';
273
        if ($input->getOption('git-friendly')) {
274
            $postDumpGitFriendlyPipeCommands = ' | sed \'s$VALUES ($VALUES\n($g\' | sed \'s$),($),\n($g\'';
275
        }
276
277
        /* @var $database DatabaseHelper */
278
        $database = $this->getDatabaseHelper();
279
280
        $mysqlClientToolConnectionString = $database->getMysqlClientToolConnectionString();
281
282
        $stripTables = $this->stripTables($input, $output);
283
        if ($stripTables) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $stripTables of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
284
            // dump structure for strip-tables
285
            $execs->add(
286
                '--no-data ' . $mysqlClientToolConnectionString .
287
                ' ' . implode(' ', $stripTables) . $this->postDumpPipeCommands()
288
            );
289
        }
290
291
        $excludeTables = $this->excludeTables($input, $output);
292
293
        // dump data for all other tables
294
        $ignore = '';
295
        foreach (array_merge($excludeTables, $stripTables) as $ignoreTable) {
296
            $ignore .= '--ignore-table=' . $this->dbSettings['dbname'] . '.' . $ignoreTable . ' ';
297
        }
298
299
        $execs->add($ignore . $mysqlClientToolConnectionString . $postDumpGitFriendlyPipeCommands . $this->postDumpPipeCommands());
300
301
        return $execs;
302
    }
303
304
    /**
305
     * @param Execs $execs
306
     * @param InputInterface $input
307
     * @param OutputInterface $output
308
     */
309
    private function runExecs(Execs $execs, InputInterface $input, OutputInterface $output)
310
    {
311
        if ($input->getOption('only-command') && !$input->getOption('print-only-filename')) {
312
            foreach ($execs->getCommands() as $command) {
313
                $output->writeln($command);
314
            }
315
        } else {
316
            if ($this->nonCommandOutput($input)) {
317
                $output->writeln(
318
                    '<comment>Start dumping database <info>' . $this->dbSettings['dbname'] .
319
                    '</info> to file <info>' . $execs->getFileName() . '</info>'
320
                );
321
            }
322
323
            $commands = $input->getOption('dry-run') ? array() : $execs->getCommands();
324
325
            foreach ($commands as $command) {
326
                if (!$this->runExec($command, $input, $output)) {
327
                    return;
328
                }
329
            }
330
331
            if (!$input->getOption('stdout') && !$input->getOption('print-only-filename')) {
332
                $output->writeln('<info>Finished</info>');
333
            }
334
        }
335
336
        if ($input->getOption('print-only-filename')) {
337
            $output->writeln($execs->getFileName());
338
        }
339
    }
340
341
    /**
342
     * @param string $command
343
     * @param InputInterface $input
344
     * @param OutputInterface $output
345
     * @return bool
346
     */
347
    private function runExec($command, InputInterface $input, OutputInterface $output)
348
    {
349
        $commandOutput = '';
350
351
        if ($input->getOption('stdout')) {
352
            passthru($command, $returnValue);
353
        } else {
354
            Exec::run($command, $commandOutput, $returnValue);
355
        }
356
357
        if ($returnValue > 0) {
358
            $output->writeln('<error>' . $commandOutput . '</error>');
359
            $output->writeln('<error>Return Code: ' . $returnValue . '. ABORTED.</error>');
360
361
            return false;
362
        }
363
364
        return true;
365
    }
366
367
    /**
368
     * @param InputInterface $input
369
     * @param OutputInterface $output
370
     * @return array
371
     */
372 View Code Duplication
    private function stripTables(InputInterface $input, OutputInterface $output)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
373
    {
374
        if (!$input->getOption('strip')) {
375
            return array();
376
        }
377
378
        $stripTables = $this->resolveDatabaseTables($input->getOption('strip'));
379
380
        if ($this->nonCommandOutput($input)) {
381
            $output->writeln(
382
                sprintf('<comment>No-data export for: <info>%s</info></comment>', implode(' ', $stripTables))
383
            );
384
        }
385
386
        return $stripTables;
387
    }
388
389
    /**
390
     * @param InputInterface $input
391
     * @param OutputInterface $output
392
     * @return array
393
     */
394 View Code Duplication
    private function excludeTables(InputInterface $input, OutputInterface $output)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
395
    {
396
        if (!$input->getOption('exclude')) {
397
            return array();
398
        }
399
400
        $excludeTables = $this->resolveDatabaseTables($input->getOption('exclude'));
401
402
        if ($this->nonCommandOutput($input)) {
403
            $output->writeln(
404
                sprintf('<comment>Excluded: <info>%s</info></comment>', implode(' ', $excludeTables))
405
            );
406
        }
407
408
        return $excludeTables;
409
    }
410
411
    /**
412
     * @param string $list space separated list of tables
413
     * @return array
414
     */
415
    private function resolveDatabaseTables($list)
416
    {
417
        $database = $this->getDatabaseHelper();
418
419
        return $database->resolveTables(
420
            explode(' ', $list),
421
            $database->getTableDefinitions($this->getCommandConfig())
422
        );
423
    }
424
425
    /**
426
     * Commands which filter mysql data. Piped to mysqldump command
427
     *
428
     * @return string
429
     */
430
    protected function postDumpPipeCommands()
431
    {
432
        return ' | LANG=C LC_CTYPE=C LC_ALL=C sed -e ' . escapeshellarg('s/DEFINER[ ]*=[ ]*[^*]*\*/\*/');
433
    }
434
435
    /**
436
     * Command which makes the dump git friendly. Piped to mysqldump command.
437
     *
438
     * @return string
439
     */
440
    protected function postDumpGitFriendlyPipeCommands()
441
    {
442
        return ' | sed \'s$VALUES ($VALUES\n($g\' | sed \'s$),($),\n($g\'';
443
    }
444
445
    /**
446
     * @param InputInterface $input
447
     * @param OutputInterface $output
448
     * @param Compressor $compressor
449
     *
450
     * @return string
451
     */
452
    protected function getFileName(InputInterface $input, OutputInterface $output, Compressor $compressor)
453
    {
454
        $nameExtension = '.sql';
455
456
        $optionAddTime = 'no';
457
        if ($input->hasParameterOption('--add-time')) {
458
            $optionAddTime = $input->getOption('add-time');
459
            if (empty($optionAddTime)) {
460
                $optionAddTime = 'suffix';
461
            }
462
        }
463
464
        list($namePrefix, $nameSuffix) = $this->getFileNamePrefixSuffix($optionAddTime);
465
466
        if (
467
            (
468
                ($fileName = $input->getArgument('filename')) === null
469
                || ($isDir = is_dir($fileName))
470
            )
471
            && !$input->getOption('stdout')
472
        ) {
473
            $defaultName = VerifyOrDie::filename(
474
                $namePrefix . $this->dbSettings['dbname'] . $nameSuffix . $nameExtension
475
            );
476
            if (isset($isDir) && $isDir) {
477
                $defaultName = rtrim($fileName, '/') . '/' . $defaultName;
478
            }
479
            if (!$input->getOption('force')) {
480
                /** @var DialogHelper $dialog */
481
                $dialog = $this->getHelper('dialog');
482
                $fileName = $dialog->ask(
483
                    $output,
484
                    '<question>Filename for SQL dump:</question> [<comment>' . $defaultName . '</comment>]',
485
                    $defaultName
486
                );
487
            } else {
488
                $fileName = $defaultName;
489
            }
490
        } else {
491
            if ($optionAddTime && $fileName !== null) {
492
                $pathParts = pathinfo($fileName);
493
                $fileName = ($pathParts['dirname'] == '.' ? '' : $pathParts['dirname'] . '/') .
494
                    $namePrefix . $pathParts['filename'] . $nameSuffix . '.' . $pathParts['extension'];
495
            }
496
        }
497
498
        $fileName = $compressor->getFileName($fileName);
499
500
        return $fileName;
501
    }
502
503
    /**
504
     * @param null|bool|string $optionAddTime [optional] true for default "suffix", other string values: "prefix", "no"
505
     * @return array
506
     */
507
    private function getFileNamePrefixSuffix($optionAddTime = null)
508
    {
509
        $namePrefix = '';
510
        $nameSuffix = '';
511
        if ($optionAddTime === null) {
512
            return array($namePrefix, $nameSuffix);
513
        }
514
515
        $timeStamp = date('Y-m-d_His');
516
517
        if (in_array($optionAddTime, array('suffix', true), true)) {
518
            $nameSuffix = '_' . $timeStamp;
519
        } elseif ($optionAddTime === 'prefix') {
520
            $namePrefix = $timeStamp . '_';
521
        } elseif ($optionAddTime !== 'no') {
522
            throw new InvalidArgumentException(
523
                sprintf(
524
                    'Invalid --add-time value %s, possible values are none (for) "suffix", "prefix" or "no"',
525
                    var_export($optionAddTime, true)
526
                )
527
            );
528
        }
529
530
        return array($namePrefix, $nameSuffix);
531
    }
532
533
    /**
534
     * @param InputInterface $input
535
     * @return bool
536
     */
537
    private function nonCommandOutput(InputInterface $input)
538
    {
539
        return
540
            !$input->getOption('stdout')
541
            && !$input->getOption('only-command')
542
            && !$input->getOption('print-only-filename');
543
    }
544
}
545