Completed
Pull Request — develop (#243)
by Tom
20:40 queued 07:41
created

DumpCommand::getTableDefinitions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 5
nc 1
nop 0
1
<?php
2
3
namespace N98\Magento\Command\Database;
4
5
use N98\Magento\Command\Database\Compressor\AbstractCompressor;
6
use N98\Util\Console\Helper\DatabaseHelper;
7
use N98\Util\OperatingSystem;
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 env.php');
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/env.php 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
     */
142
    private function getTableDefinitions()
143
    {
144
        /** @var DatabaseHelper $database */
145
        $database = $this->getHelper('database');
146
        $tableDefinitions = $database->getTableDefinitions(
147
            $this->getCommandConfig()
148
        );
149
150
        return $tableDefinitions;
151
    }
152
153
    /**
154
     * Generate help for table definitions
155
     *
156
     * @return string
157
     */
158
    public function getTableDefinitionHelp()
159
    {
160
        $messages = array();
161
        $this->commandConfig = $this->getCommandConfig();
162
        $messages[] = '';
163
        $messages[] = '<comment>Strip option</comment>';
164
        $messages[] = ' Separate each table to strip by a space.';
165
        $messages[] = ' You can use wildcards like * and ? in the table names to strip multiple tables.';
166
        $messages[] = ' In addition you can specify pre-defined table groups, that start with an @';
167
        $messages[] = ' Example: "dataflow_batch_export unimportant_module_* @log';
168
        $messages[] = '';
169
        $messages[] = '<comment>Available Table Groups</comment>';
170
171
        $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...
172
        foreach ($definitions as $id => $definition) {
173
            $description = isset($definition['description']) ? $definition['description'] : '';
174
            /** @TODO:
175
             * Column-Wise formatting of the options, see InputDefinition::asText for code to pad by the max length,
176
             * but I do not like to copy and paste ..
177
             */
178
            $messages[] = ' <info>@' . $id . '</info> ' . $description;
179
        }
180
181
        return implode(PHP_EOL, $messages);
182
    }
183
184
    public function getHelp()
185
    {
186
        return parent::getHelp() . PHP_EOL
187
        . $this->getCompressionHelp() . PHP_EOL
188
        . $this->getTableDefinitionHelp();
189
    }
190
191
    /**
192
     * @param InputInterface $input
193
     * @param OutputInterface $output
194
     * @return int|void
195
     */
196
    protected function execute(InputInterface $input, OutputInterface $output)
197
    {
198
        $this->detectDbSettings($output);
199
200
        if ($this->nonCommandOutput($input)) {
201
            $this->writeSection($output, 'Dump MySQL Database');
202
        }
203
204
        $execs = $this->createExecs($input, $output);
205
206
        $this->runExecs($execs, $input, $output);
207
    }
208
209
    /**
210
     * @param InputInterface $input
211
     * @param OutputInterface $output
212
     * @return Execs
213
     */
214
    private function createExecs(InputInterface $input, OutputInterface $output)
215
    {
216
        $execs = new Execs('mysqldump');
217
        $execs->setCompression($input->getOption('compression'));
218
        $execs->setFileName($this->getFileName($input, $output, $execs->getCompressor()));
219
220
        if (!$input->getOption('no-single-transaction')) {
221
            $execs->addOptions('--single-transaction --quick');
222
        }
223
224
        if ($input->getOption('human-readable')) {
225
            $execs->addOptions('--complete-insert --skip-extended-insert ');
226
        }
227
228
        if ($input->getOption('add-routines')) {
229
            $execs->addOptions('--routines ');
230
        }
231
232
        $database = $this->getDatabaseHelper();
233
        $stripTables = $this->stripTables($input, $output);
234
        if ($stripTables) {
235
            // dump structure for strip-tables
236
            $execs->add(
237
                '--no-data ' . $database->getMysqlClientToolConnectionString() .
238
                ' ' . implode(' ', $stripTables) . $this->postDumpPipeCommands()
239
            );
240
241
            // dump data for all other tables
242
            $ignore = '';
243
            foreach ($stripTables as $stripTable) {
244
                $ignore .= '--ignore-table=' . $this->dbSettings['dbname'] . '.' . $stripTable . ' ';
245
            }
246
            $execs->add(
247
                $ignore . $database->getMysqlClientToolConnectionString() . $this->postDumpPipeCommands()
248
            );
249
250
            return $execs;
251
        } else {
252
            $execs->add(
253
                $database->getMysqlClientToolConnectionString() . $this->postDumpPipeCommands()
254
            );
255
256
            return $execs;
257
        }
258
    }
259
260
    /**
261
     * @param Execs $execs
262
     * @param InputInterface $input
263
     * @param OutputInterface $output
264
     */
265
    private function runExecs(Execs $execs, InputInterface $input, OutputInterface $output)
266
    {
267
        if ($input->getOption('only-command') && !$input->getOption('print-only-filename')) {
268
            foreach ($execs->getCommands() as $command) {
269
                $output->writeln($command);
270
            }
271
        } else {
272
            if ($this->nonCommandOutput($input)) {
273
                $output->writeln(
274
                    '<comment>Start dumping database <info>' . $this->dbSettings['dbname'] .
275
                    '</info> to file <info>' . $execs->getFileName() . '</info>'
276
                );
277
            }
278
279
            $commands = $input->getOption('dry-run') ? array() : $execs->getCommands();
280
281
            foreach ($commands as $command) {
282
                if (!$this->runExec($command, $input, $output)) {
283
                    return;
284
                }
285
            }
286
287
            if (!$input->getOption('stdout') && !$input->getOption('print-only-filename')) {
288
                $output->writeln('<info>Finished</info>');
289
            }
290
        }
291
292
        if ($input->getOption('print-only-filename')) {
293
            $output->writeln($execs->getFileName());
294
        }
295
    }
296
297
    /**
298
     * @param string $command
299
     * @param InputInterface $input
300
     * @param OutputInterface $output
301
     * @return bool
302
     */
303
    private function runExec($command, InputInterface $input, OutputInterface $output)
304
    {
305
        $commandOutput = '';
306
307
        if ($input->getOption('stdout')) {
308
            passthru($command, $returnValue);
309
        } else {
310
            exec($command, $commandOutput, $returnValue);
311
        }
312
313
        if ($returnValue > 0) {
314
            $output->writeln([
315
                '<error>' . implode(PHP_EOL, $commandOutput) . '</error>',
316
                '<error>Return Code: ' . $returnValue . '. ABORTED.</error>',
317
            ]);
318
            return false;
319
        }
320
        return true;
321
    }
322
323
    /**
324
     * @param InputInterface $input
325
     * @param OutputInterface $output
326
     * @return false|array
327
     */
328
    private function stripTables(InputInterface $input, OutputInterface $output)
329
    {
330
        if (!$input->getOption('strip')) {
331
            return false;
332
        }
333
334
        $stripTables = $this->getDatabaseHelper()->resolveTables(
335
            explode(' ', $input->getOption('strip')),
336
            $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...
337
        );
338
339
        if ($this->nonCommandOutput($input)) {
340
            $output->writeln(
341
                '<comment>No-data export for: <info>' . implode(' ', $stripTables) . '</info></comment>'
342
            );
343
        }
344
345
        return $stripTables;
346
    }
347
348
    /**
349
     * Commands which filter mysql data. Piped to mysqldump command
350
     *
351
     * @return string
352
     */
353
    protected function postDumpPipeCommands()
354
    {
355
        return ' | sed -e ' . escapeshellarg('s/DEFINER[ ]*=[ ]*[^*]*\*/\*/');
356
    }
357
358
    /**
359
     * @param InputInterface $input
360
     * @param OutputInterface $output
361
     * @param AbstractCompressor $compressor
362
     * @return string
363
     */
364
    protected function getFileName(
365
        InputInterface $input,
366
        OutputInterface $output,
367
        AbstractCompressor $compressor
368
    ) {
369
        $namePrefix = '';
370
        $nameSuffix = '';
371
        $nameExtension = '.sql';
372
373
        if ($input->getOption('add-time') !== false) {
374
            $timeStamp = date('Y-m-d_His');
375
376
            if ($input->getOption('add-time') == 'suffix') {
377
                $nameSuffix = '_' . $timeStamp;
378
            } else {
379
                $namePrefix = $timeStamp . '_';
380
            }
381
        }
382
383
        if (
384
            (
385
                ($fileName = $input->getArgument('filename')) === null
386
                || ($isDir = is_dir($fileName))
387
            )
388
            && !$input->getOption('stdout')
389
        ) {
390
            $defaultName = $namePrefix . $this->dbSettings['dbname'] . $nameSuffix . $nameExtension;
391
            if (isset($isDir) && $isDir) {
392
                $defaultName = rtrim($fileName, '/') . '/' . $defaultName;
393
            }
394
            if (!$input->getOption('force')) {
395
                /** @var DialogHelper $dialog */
396
                $dialog = $this->getHelper('dialog');
397
                $fileName = $dialog->ask(
398
                    $output,
399
                    '<question>Filename for SQL dump:</question> [<comment>' . $defaultName . '</comment>]',
400
                    $defaultName
401
                );
402
            } else {
403
                $fileName = $defaultName;
404
            }
405
        } else {
406
            if ($input->getOption('add-time')) {
407
                $pathParts = pathinfo($fileName);
408
                $fileName = ($pathParts['dirname'] == '.' ? '' : $pathParts['dirname'] . '/') .
409
                    $namePrefix . $pathParts['filename'] . $nameSuffix . '.' . $pathParts['extension'];
410
            }
411
        }
412
413
        $fileName = $compressor->getFileName($fileName);
414
415
        return $fileName;
416
    }
417
418
    /**
419
     * @param InputInterface $input
420
     * @return bool
421
     */
422
    private function nonCommandOutput(InputInterface $input)
423
    {
424
        return
425
            !$input->getOption('stdout')
426
            && !$input->getOption('only-command')
427
            && !$input->getOption('print-only-filename');
428
    }
429
}
430