Completed
Push — master ( fb5c6c...3561cd )
by Tom
04:39
created

DumpCommand::getFileName()   D

Complexity

Conditions 9
Paths 7

Size

Total Lines 43
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

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