Completed
Push — develop ( 18b2a7...266658 )
by Tom
07:22
created

DumpCommand::excludeTables()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 10

Duplication

Lines 19
Ratio 100 %

Importance

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