Completed
Push — develop ( f63da3...8c2b94 )
by Tom
04:32
created

DumpCommand::getTableDefinitionHelp()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 25
rs 8.8571
cc 3
eloc 16
nc 3
nop 0
1
<?php
2
3
namespace N98\Magento\Command\Database;
4
5
use N98\Magento\Command\Database\Compressor\AbstractCompressor;
6
use N98\Util\OperatingSystem;
7
use RuntimeException;
8
use Symfony\Component\Console\Helper\DialogHelper;
9
use Symfony\Component\Console\Input\InputArgument;
10
use Symfony\Component\Console\Input\InputInterface;
11
use Symfony\Component\Console\Input\InputOption;
12
use Symfony\Component\Console\Output\OutputInterface;
13
14
class DumpCommand extends AbstractDatabaseCommand
15
{
16
    /**
17
     * @var array
18
     */
19
    protected $tableDefinitions = null;
20
21
    /**
22
     * @var array
23
     */
24
    protected $commandConfig = null;
25
26
    protected function configure()
27
    {
28
        $this
29
            ->setName('db:dump')
30
            ->addArgument('filename', InputArgument::OPTIONAL, 'Dump filename')
31
            ->addOption(
32
                'add-time',
33
                't',
34
                InputOption::VALUE_OPTIONAL,
35
                'Adds time to filename (only if filename was not provided)'
36
            )
37
            ->addOption(
38
                'compression',
39
                'c',
40
                InputOption::VALUE_REQUIRED,
41
                'Compress the dump file using one of the supported algorithms'
42
            )
43
            ->addOption(
44
                'only-command',
45
                null,
46
                InputOption::VALUE_NONE,
47
                'Print only mysqldump command. 
48
                Do not execute'
49
            )
50
            ->addOption(
51
                'print-only-filename',
52
                null,
53
                InputOption::VALUE_NONE,
54
                'Execute and prints no output except the dump filename'
55
            )
56
            ->addOption('dry-run', null, InputOption::VALUE_NONE, 'do everything but the dump')
57
            ->addOption(
58
                'no-single-transaction',
59
                null,
60
                InputOption::VALUE_NONE,
61
                'Do not use single-transaction (not recommended, this is blocking)'
62
            )
63
            ->addOption(
64
                'human-readable',
65
                null,
66
                InputOption::VALUE_NONE,
67
                'Use a single insert with column names per row. Useful to track database differences. Use ' .
68
                'db:import --optimize for speeding up the import.'
69
            )
70
            ->addOption(
71
                'add-routines',
72
                null,
73
                InputOption::VALUE_NONE,
74
                'Include stored routines in dump (procedures & functions)'
75
            )
76
            ->addOption('stdout', null, InputOption::VALUE_NONE, 'Dump to stdout')
77
            ->addOption(
78
                'strip',
79
                's',
80
                InputOption::VALUE_OPTIONAL,
81
                'Tables to strip (dump only structure of those tables)'
82
            )
83
            ->addOption('force', 'f', InputOption::VALUE_NONE, 'Do not prompt if all options are defined')
84
            ->setDescription('Dumps database with mysqldump cli client according to informations from local.xml');
85
86
        $help = <<<HELP
87
Dumps configured magento database with `mysqldump`.
88
You must have installed the MySQL client tools.
89
90
On debian systems run `apt-get install mysql-client` to do that.
91
92
The command reads app/etc/local.xml to find the correct settings.
93
If you like to skip data of some tables you can use the --strip option.
94
The strip option creates only the structure of the defined tables and
95
forces `mysqldump` to skip the data.
96
97
Dumps your database and excludes some tables. This is useful i.e. for development.
98
99
Separate each table to strip by a space.
100
You can use wildcards like * and ? in the table names to strip multiple tables.
101
In addition you can specify pre-defined table groups, that start with an @
102
Example: "dataflow_batch_export unimportant_module_* @log
103
104
   $ n98-magerun.phar db:dump --strip="@stripped"
105
106
Available Table Groups:
107
108
* @log Log tables
109
* @dataflowtemp Temporary tables of the dataflow import/export tool
110
* @stripped Standard definition for a stripped dump (logs and dataflow)
111
* @sales Sales data (orders, invoices, creditmemos etc)
112
* @customers Customer data
113
* @trade Current trade data (customers and orders). You usally do not want those in developer systems.
114
* @development Removes logs and trade data so developers do not have to work with real customer data
115
116
Extended: https://github.com/netz98/n98-magerun/wiki/Stripped-Database-Dumps
117
118
See it in action: http://youtu.be/ttjZHY6vThs
119
120
- If you like to prepend a timestamp to the dump name the --add-time option can be used.
121
122
- The command comes with a compression function. Add i.e. `--compression=gz` to dump directly in
123
 gzip compressed file.
124
125
HELP;
126
        $this->setHelp($help);
127
    }
128
129
    /**
130
     * @return bool
131
     */
132
    public function isEnabled()
133
    {
134
        return function_exists('exec') && !OperatingSystem::isWindows();
135
    }
136
137
    /**
138
     * @return array
139
     *
140
     * @deprecated Use database helper
141
     * @throws RuntimeException
142
     */
143
    public function getTableDefinitions()
144
    {
145
        $this->commandConfig = $this->getCommandConfig();
146
147
        if (is_null($this->tableDefinitions)) {
148
            $this->tableDefinitions = array();
149
            if (isset($this->commandConfig['table-groups'])) {
150
                $tableGroups = $this->commandConfig['table-groups'];
151 View Code Duplication
                foreach ($tableGroups as $index => $definition) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
152
                    $description = isset($definition['description']) ? $definition['description'] : '';
153
                    if (!isset($definition['id'])) {
154
                        throw new RuntimeException('Invalid definition of table-groups (id missing) Index: ' . $index);
155
                    }
156
                    if (!isset($definition['id'])) {
157
                        throw new RuntimeException(
158
                            'Invalid definition of table-groups (tables missing) Id: ' . $definition['id']
159
                        );
160
                    }
161
162
                    $this->tableDefinitions[$definition['id']] = array(
163
                        'tables'      => $definition['tables'],
164
                        'description' => $description,
165
                    );
166
                }
167
            };
168
        }
169
170
        return $this->tableDefinitions;
171
    }
172
173
    /**
174
     * Generate help for table definitions
175
     *
176
     * @return string
177
     */
178
    public function getTableDefinitionHelp()
179
    {
180
        $messages = array();
181
        $this->commandConfig = $this->getCommandConfig();
182
        $messages[] = '';
183
        $messages[] = '<comment>Strip option</comment>';
184
        $messages[] = ' Separate each table to strip by a space.';
185
        $messages[] = ' You can use wildcards like * and ? in the table names to strip multiple tables.';
186
        $messages[] = ' In addition you can specify pre-defined table groups, that start with an @';
187
        $messages[] = ' Example: "dataflow_batch_export unimportant_module_* @log';
188
        $messages[] = '';
189
        $messages[] = '<comment>Available Table Groups</comment>';
190
191
        $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...
192
        foreach ($definitions as $id => $definition) {
193
            $description = isset($definition['description']) ? $definition['description'] : '';
194
            /** @TODO:
195
             * Column-Wise formatting of the options, see InputDefinition::asText for code to pad by the max length,
196
             * but I do not like to copy and paste ..
197
             */
198
            $messages[] = ' <info>@' . $id . '</info> ' . $description;
199
        }
200
201
        return implode(PHP_EOL, $messages);
202
    }
203
204
    public function getHelp()
205
    {
206
        return parent::getHelp() . PHP_EOL
207
        . $this->getCompressionHelp() . PHP_EOL
208
        . $this->getTableDefinitionHelp();
209
    }
210
211
    /**
212
     * @param InputInterface $input
213
     * @param OutputInterface $output
214
     * @return int|void
215
     */
216
    protected function execute(InputInterface $input, OutputInterface $output)
217
    {
218
        $this->detectDbSettings($output);
219
220
        if ($this->nonCommandOutput($input)) {
221
            $this->writeSection($output, 'Dump MySQL Database');
222
        }
223
224
        $execs = $this->createExecs($input, $output);
225
226
        $this->runExecs($execs, $input, $output);
227
    }
228
229
    /**
230
     * @param InputInterface $input
231
     * @param OutputInterface $output
232
     * @return Execs
233
     */
234
    private function createExecs(InputInterface $input, OutputInterface $output)
235
    {
236
        $execs = new Execs('mysqldump');
237
        $execs->setCompression($input->getOption('compression'));
238
        $execs->setFileName($this->getFileName($input, $output, $execs->getCompressor()));
239
240
        if (!$input->getOption('no-single-transaction')) {
241
            $execs->addOptions('--single-transaction --quick');
242
        }
243
244
        if ($input->getOption('human-readable')) {
245
            $execs->addOptions('--complete-insert --skip-extended-insert ');
246
        }
247
248
        if ($input->getOption('add-routines')) {
249
            $execs->addOptions('--routines ');
250
        }
251
252
        $database = $this->getDatabaseHelper();
253
        $stripTables = $this->stripTables($input, $output);
254
        if ($stripTables) {
255
            // dump structure for strip-tables
256
            $execs->add(
257
                '--no-data ' . $database->getMysqlClientToolConnectionString() .
258
                ' ' . implode(' ', $stripTables) . $this->postDumpPipeCommands()
259
            );
260
261
            // dump data for all other tables
262
            $ignore = '';
263
            foreach ($stripTables as $stripTable) {
264
                $ignore .= '--ignore-table=' . $this->dbSettings['dbname'] . '.' . $stripTable . ' ';
265
            }
266
            $execs->add(
267
                $ignore . $database->getMysqlClientToolConnectionString() . $this->postDumpPipeCommands()
268
            );
269
270
            return $execs;
271
        } else {
272
            $execs->add(
273
                $database->getMysqlClientToolConnectionString() . $this->postDumpPipeCommands()
274
            );
275
276
            return $execs;
277
        }
278
    }
279
280
    /**
281
     * @param Execs $execs
282
     * @param InputInterface $input
283
     * @param OutputInterface $output
284
     */
285
    private function runExecs(Execs $execs, InputInterface $input, OutputInterface $output)
286
    {
287
        if ($input->getOption('only-command') && !$input->getOption('print-only-filename')) {
288
            foreach ($execs->getCommands() as $command) {
289
                $output->writeln($command);
290
            }
291
        } else {
292
            if ($this->nonCommandOutput($input)) {
293
                $output->writeln(
294
                    '<comment>Start dumping database <info>' . $this->dbSettings['dbname'] .
295
                    '</info> to file <info>' . $execs->getFileName() . '</info>'
296
                );
297
            }
298
299
            $commands = $input->getOption('dry-run') ? array() : $execs->getCommands();
300
301
            foreach ($commands as $command) {
302
                if (!$this->runExec($command, $input, $output)) {
303
                    return;
304
                }
305
            }
306
307
            if (!$input->getOption('stdout') && !$input->getOption('print-only-filename')) {
308
                $output->writeln('<info>Finished</info>');
309
            }
310
        }
311
312
        if ($input->getOption('print-only-filename')) {
313
            $output->writeln($execs->getFileName());
314
        }
315
    }
316
317
    /**
318
     * @param string $command
319
     * @param InputInterface $input
320
     * @param OutputInterface $output
321
     */
322
    private function runExec($command, InputInterface $input, OutputInterface $output)
323
    {
324
        $commandOutput = '';
325
326
        if ($input->getOption('stdout')) {
327
            passthru($command, $returnValue);
328
        } else {
329
            exec($command, $commandOutput, $returnValue);
330
        }
331
332
        if ($returnValue > 0) {
333
            $output->writeln([
334
                '<error>' . implode(PHP_EOL, $commandOutput) . '</error>',
335
                '<error>Return Code: ' . $returnValue . '. ABORTED.</error>',
336
            ]);
337
            return;
338
        }
339
    }
340
341
    /**
342
     * @param InputInterface $input
343
     * @param OutputInterface $output
344
     * @return false|array
345
     */
346
    private function stripTables(InputInterface $input, OutputInterface $output)
347
    {
348
        if (!$input->getOption('strip')) {
349
            return false;
350
        }
351
352
        $stripTables = $this->getDatabaseHelper()->resolveTables(
353
            explode(' ', $input->getOption('strip')),
354
            $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...
355
        );
356
357
        if ($this->nonCommandOutput($input)) {
358
            $output->writeln(
359
                '<comment>No-data export for: <info>' . implode(' ', $stripTables) . '</info></comment>'
360
            );
361
        }
362
363
        return $stripTables;
364
    }
365
366
    /**
367
     * Commands which filter mysql data. Piped to mysqldump command
368
     *
369
     * @return string
370
     */
371
    protected function postDumpPipeCommands()
372
    {
373
        return ' | sed -e ' . escapeshellarg('s/DEFINER[ ]*=[ ]*[^*]*\*/\*/');
374
    }
375
376
    /**
377
     * @param InputInterface $input
378
     * @param OutputInterface $output
379
     * @param AbstractCompressor $compressor
380
     * @return string
381
     */
382
    protected function getFileName(
383
        InputInterface $input,
384
        OutputInterface $output,
385
        AbstractCompressor $compressor
386
    ) {
387
        $namePrefix = '';
388
        $nameSuffix = '';
389
        $nameExtension = '.sql';
390
391
        if ($input->getOption('add-time') !== false) {
392
            $timeStamp = date('Y-m-d_His');
393
394
            if ($input->getOption('add-time') == 'suffix') {
395
                $nameSuffix = '_' . $timeStamp;
396
            } else {
397
                $namePrefix = $timeStamp . '_';
398
            }
399
        }
400
401
        if (
402
            (
403
                ($fileName = $input->getArgument('filename')) === null
404
                || ($isDir = is_dir($fileName))
405
            )
406
            && !$input->getOption('stdout')
407
        ) {
408
            $defaultName = $namePrefix . $this->dbSettings['dbname'] . $nameSuffix . $nameExtension;
409
            if (isset($isDir) && $isDir) {
410
                $defaultName = rtrim($fileName, '/') . '/' . $defaultName;
411
            }
412
            if (!$input->getOption('force')) {
413
                /** @var DialogHelper $dialog */
414
                $dialog = $this->getHelperSet()->get('dialog');
415
                $fileName = $dialog->ask(
416
                    $output,
417
                    '<question>Filename for SQL dump:</question> [<comment>' . $defaultName . '</comment>]',
418
                    $defaultName
419
                );
420
            } else {
421
                $fileName = $defaultName;
422
            }
423
        } else {
424
            if ($input->getOption('add-time')) {
425
                $pathParts = pathinfo($fileName);
426
                $fileName = ($pathParts['dirname'] == '.' ? '' : $pathParts['dirname'] . '/') .
427
                    $namePrefix . $pathParts['filename'] . $nameSuffix . '.' . $pathParts['extension'];
428
            }
429
        }
430
431
        $fileName = $compressor->getFileName($fileName);
432
433
        return $fileName;
434
    }
435
436
    /**
437
     * @param InputInterface $input
438
     * @return bool
439
     */
440
    private function nonCommandOutput(InputInterface $input)
441
    {
442
        return
443
            !$input->getOption('stdout')
444
            && !$input->getOption('only-command')
445
            && !$input->getOption('print-only-filename');
446
    }
447
}
448