Completed
Pull Request — master (#177)
by
unknown
04:33
created

src/N98/Magento/Command/Database/DumpCommand.php (11 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace N98\Magento\Command\Database;
4
5
use N98\Util\OperatingSystem;
6
use RuntimeException;
7
use Symfony\Component\Console\Helper\DialogHelper;
8
use Symfony\Component\Console\Input\InputArgument;
9
use Symfony\Component\Console\Input\InputInterface;
10
use Symfony\Component\Console\Input\InputOption;
11
use Symfony\Component\Console\Output\OutputInterface;
12
13
class DumpCommand extends AbstractDatabaseCommand
14
{
15
    /**
16
     * @var array
17
     */
18
    protected $tableDefinitions = null;
19
20
    /**
21
     * @var array
22
     */
23
    protected $commandConfig = null;
24
25
    protected function configure()
26
    {
27
        $this
28
            ->setName('db:dump')
29
            ->addArgument('filename', InputArgument::OPTIONAL, 'Dump filename')
30
            ->addOption('add-time', 't', InputOption::VALUE_OPTIONAL, 'Adds time to filename (only if filename was not provided)')
0 ignored issues
show
This line exceeds maximum limit of 120 characters; contains 130 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
31
            ->addOption('compression', 'c', InputOption::VALUE_REQUIRED, 'Compress the dump file using one of the supported algorithms')
0 ignored issues
show
This line exceeds maximum limit of 120 characters; contains 136 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
32
            ->addOption('only-command', null, InputOption::VALUE_NONE, 'Print only mysqldump command. Do not execute')
33
            ->addOption('print-only-filename', null, InputOption::VALUE_NONE, 'Execute and prints no output except the dump filename')
0 ignored issues
show
This line exceeds maximum limit of 120 characters; contains 134 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
34
            ->addOption('no-single-transaction', null, InputOption::VALUE_NONE, 'Do not use single-transaction (not recommended, this is blocking)')
0 ignored issues
show
This line exceeds maximum limit of 120 characters; contains 148 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
35
            ->addOption('human-readable', null, InputOption::VALUE_NONE, 'Use a single insert with column names per row. Useful to track database differences. Use db:import --optimize for speeding up the import.')
0 ignored issues
show
This line exceeds maximum limit of 120 characters; contains 213 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
36
            ->addOption('add-routines', null, InputOption::VALUE_NONE, 'Include stored routines in dump (procedures & functions)')
0 ignored issues
show
This line exceeds maximum limit of 120 characters; contains 130 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
37
            ->addOption('stdout', null, InputOption::VALUE_NONE, 'Dump to stdout')
38
            ->addOption('strip', 's', InputOption::VALUE_OPTIONAL, 'Tables to strip (dump only structure of those tables)')
0 ignored issues
show
This line exceeds maximum limit of 120 characters; contains 123 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
39
            ->addOption('force', 'f', InputOption::VALUE_NONE, 'Do not prompt if all options are defined')
40
            ->setDescription('Dumps database with mysqldump cli client according to informations from local.xml');
41
42
        $help = <<<HELP
43
Dumps configured magento database with `mysqldump`.
44
You must have installed the MySQL client tools.
45
46
On debian systems run `apt-get install mysql-client` to do that.
47
48
The command reads app/etc/local.xml to find the correct settings.
49
If you like to skip data of some tables you can use the --strip option.
50
The strip option creates only the structure of the defined tables and
51
forces `mysqldump` to skip the data.
52
53
Dumps your database and excludes some tables. This is useful i.e. for development.
54
55
Separate each table to strip by a space.
56
You can use wildcards like * and ? in the table names to strip multiple tables.
57
In addition you can specify pre-defined table groups, that start with an @
58
Example: "dataflow_batch_export unimportant_module_* @log
59
60
   $ n98-magerun.phar db:dump --strip="@stripped"
61
62
Available Table Groups:
63
64
* @log Log tables
65
* @dataflowtemp Temporary tables of the dataflow import/export tool
66
* @stripped Standard definition for a stripped dump (logs and dataflow)
67
* @sales Sales data (orders, invoices, creditmemos etc)
68
* @customers Customer data
69
* @trade Current trade data (customers and orders). You usally do not want those in developer systems.
70
* @development Removes logs and trade data so developers do not have to work with real customer data
71
72
Extended: https://github.com/netz98/n98-magerun/wiki/Stripped-Database-Dumps
73
74
See it in action: http://youtu.be/ttjZHY6vThs
75
76
- If you like to prepend a timestamp to the dump name the --add-time option can be used.
77
78
- The command comes with a compression function. Add i.e. `--compression=gz` to dump directly in
79
 gzip compressed file.
80
81
HELP;
82
        $this->setHelp($help);
83
84
    }
85
86
    /**
87
     * @return bool
88
     */
89
    public function isEnabled()
90
    {
91
        return function_exists('exec') && !OperatingSystem::isWindows();
92
    }
93
94
    /**
95
     * @return array
96
     *
97
     * @deprecated Use database helper
98
     * @throws RuntimeException
99
     */
100
    public function getTableDefinitions()
101
    {
102
        $this->commandConfig = $this->getCommandConfig();
103
104
        if (is_null($this->tableDefinitions)) {
105
            $this->tableDefinitions = array();
106
            if (isset($this->commandConfig['table-groups'])) {
107
                $tableGroups = $this->commandConfig['table-groups'];
108 View Code Duplication
                foreach ($tableGroups as $index=>$definition) {
109
                    $description = isset($definition['description']) ? $definition['description'] : '';
110
                    if (!isset($definition['id'])) {
111
                        throw new RuntimeException('Invalid definition of table-groups (id missing) Index: ' . $index);
112
                    }
113
                    if (!isset($definition['id'])) {
114
                        throw new RuntimeException('Invalid definition of table-groups (tables missing) Id: '
115
                            . $definition['id']
116
                        );
117
                    }
118
119
                    $this->tableDefinitions[$definition['id']] = array(
120
                        'tables'      => $definition['tables'],
121
                        'description' => $description,
122
                    );
123
                }
124
            };
125
        }
126
127
        return $this->tableDefinitions;
128
    }
129
130
    /**
131
     * Generate help for table definitions
132
     *
133
     * @return string
134
     */
135
    public function getTableDefinitionHelp()
136
    {
137
        $messages = array();
138
        $this->commandConfig = $this->getCommandConfig();
139
        $messages[] = '';
140
        $messages[] = '<comment>Strip option</comment>';
141
        $messages[] = ' Separate each table to strip by a space.';
142
        $messages[] = ' You can use wildcards like * and ? in the table names to strip multiple tables.';
143
        $messages[] = ' In addition you can specify pre-defined table groups, that start with an @';
144
        $messages[] = ' Example: "dataflow_batch_export unimportant_module_* @log';
145
        $messages[] = '';
146
        $messages[] = '<comment>Available Table Groups</comment>';
147
148
        $definitions = $this->getTableDefinitions();
149
        foreach ($definitions as $id => $definition) {
150
            $description = isset($definition['description']) ? $definition['description'] : '';
151
            /** @TODO:
152
             * Column-Wise formatting of the options, see InputDefinition::asText for code to pad by the max length,
153
             * but I do not like to copy and paste ..
154
             */
155
            $messages[] = ' <info>@' . $id . '</info> ' . $description;
156
        }
157
158
        return implode(PHP_EOL, $messages);
159
    }
160
161
    public function getHelp()
162
    {
163
        return parent::getHelp() . PHP_EOL
164
            . $this->getCompressionHelp() . PHP_EOL
165
            . $this->getTableDefinitionHelp();
166
    }
167
168
    /**
169
     * @param \Symfony\Component\Console\Input\InputInterface $input
170
     * @param \Symfony\Component\Console\Output\OutputInterface $output
171
     * @return int|void
172
     */
173
    protected function execute(InputInterface $input, OutputInterface $output)
174
    {
175
        $this->detectDbSettings($output);
176
177
        if (!$input->getOption('stdout') && !$input->getOption('only-command')
178
            && !$input->getOption('print-only-filename')
179
        ) {
180
            $this->writeSection($output, 'Dump MySQL Database');
181
        }
182
183
        $compressor = $this->getCompressor($input->getOption('compression'));
184
        $fileName   = $this->getFileName($input, $output, $compressor);
185
186
        $stripTables = false;
187
        if ($input->getOption('strip')) {
188
            $stripTables = $this->getHelper('database')->resolveTables(explode(' ', $input->getOption('strip')), $this->getTableDefinitions());
0 ignored issues
show
This line exceeds maximum limit of 120 characters; contains 143 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
189
            if (!$input->getOption('stdout') && !$input->getOption('only-command')
190
                && !$input->getOption('print-only-filename')
191
            ) {
192
                $output->writeln('<comment>No-data export for: <info>' . implode(' ', $stripTables)
193
                    . '</info></comment>'
194
                );
195
            }
196
        }
197
198
        $dumpOptions = '';
199
        if (!$input->getOption('no-single-transaction')) {
200
            $dumpOptions = '--single-transaction --quick ';
201
        }
202
203
        if ($input->getOption('human-readable')) {
204
            $dumpOptions .= '--complete-insert --skip-extended-insert ';
205
        }
206
207
        if ($input->getOption('add-routines')) {
208
            $dumpOptions .= '--routines ';
209
        }
210
        $execs = array();
211
212
        if (!$stripTables) {
213
            $exec = 'mysqldump ' . $dumpOptions . $this->getHelper('database')->getMysqlClientToolConnectionString();
214
            $exec .= $this->postDumpPipeCommands();
215
            $exec = $compressor->getCompressingCommand($exec);
216
            if (!$input->getOption('stdout')) {
217
                $exec .= ' > ' . escapeshellarg($fileName);
218
            }
219
            $execs[] = $exec;
220
        } else {
221
            // dump structure for strip-tables
222
            $exec = 'mysqldump ' . $dumpOptions . '--no-data ' . $this->getHelper('database')->getMysqlClientToolConnectionString();
0 ignored issues
show
This line exceeds maximum limit of 120 characters; contains 132 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
223
            $exec .= ' ' . implode(' ', $stripTables);
224
            $exec .= $this->postDumpPipeCommands();
225
            $exec = $compressor->getCompressingCommand($exec);
226
            if (!$input->getOption('stdout')) {
227
                $exec .= ' > ' . escapeshellarg($fileName);
228
            }
229
            $execs[] = $exec;
230
231
            $ignore = '';
232
            foreach ($stripTables as $stripTable) {
233
                $ignore .= '--ignore-table=' . $this->dbSettings['dbname'] . '.' . $stripTable . ' ';
234
            }
235
236
            // dump data for all other tables
237
            $exec = 'mysqldump ' . $dumpOptions . $ignore . $this->getHelper('database')->getMysqlClientToolConnectionString();
0 ignored issues
show
This line exceeds maximum limit of 120 characters; contains 127 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
238
            $exec .= $this->postDumpPipeCommands();
239
            $exec = $compressor->getCompressingCommand($exec);
240
            if (!$input->getOption('stdout')) {
241
                $exec .= ' >> ' . escapeshellarg($fileName);
242
            }
243
            $execs[] = $exec;
244
        }
245
246
        $this->runExecs($execs, $fileName, $input, $output);
247
    }
248
249
    /**
250
     * @param array $execs
251
     * @param string $fileName
252
     * @param InputInterface $input
253
     * @param OutputInterface $output
254
     */
255
    private function runExecs(array $execs, $fileName, InputInterface $input, OutputInterface $output)
256
    {
257
        if ($input->getOption('only-command') && !$input->getOption('print-only-filename')) {
258
            foreach ($execs as $exec) {
259
                $output->writeln($exec);
260
            }
261
        } else {
262
            if (!$input->getOption('stdout') && !$input->getOption('only-command')
263
                && !$input->getOption('print-only-filename')
264
            ) {
265
                $output->writeln('<comment>Start dumping database <info>' . $this->dbSettings['dbname']
266
                    . '</info> to file <info>' . $fileName . '</info>'
267
                );
268
            }
269
270
            foreach ($execs as $exec) {
271
                $commandOutput = '';
272
                if ($input->getOption('stdout')) {
273
                    passthru($exec, $returnValue);
274
                } else {
275
                    exec($exec, $commandOutput, $returnValue);
276
                }
277
                if ($returnValue > 0) {
278
                    $output->writeln('<error>' . implode(PHP_EOL, $commandOutput) . '</error>');
279
                    $output->writeln('<error>Return Code: ' . $returnValue . '. ABORTED.</error>');
280
281
                    return;
282
                }
283
            }
284
285
            if (!$input->getOption('stdout') && !$input->getOption('print-only-filename')) {
286
                $output->writeln('<info>Finished</info>');
287
            }
288
        }
289
290
        if ($input->getOption('print-only-filename')) {
291
            $output->writeln($fileName);
292
        }
293
    }
294
295
    /**
296
     * Commands which filter mysql data. Piped to mysqldump command
297
     *
298
     * @return string
299
     */
300
    protected function postDumpPipeCommands()
301
    {
302
        return ' | sed -e ' . escapeshellarg('s/DEFINER[ ]*=[ ]*[^*]*\*/\*/');
303
    }
304
305
    /**
306
     * @param \Symfony\Component\Console\Input\InputInterface $input
307
     * @param \Symfony\Component\Console\Output\OutputInterface $output
308
     * @param \N98\Magento\Command\Database\Compressor\AbstractCompressor $compressor
309
     * @return string
310
     */
311
    protected function getFileName(InputInterface $input, OutputInterface $output,
312
        Compressor\AbstractCompressor $compressor
313
    ) {
314
        $namePrefix    = '';
315
        $nameSuffix    = '';
316
        $nameExtension = '.sql';
317
318
        if ($input->getOption('add-time') !== false) {
319
            $timeStamp = date('Y-m-d_His');
320
321
            if ($input->getOption('add-time') == 'suffix') {
322
                $nameSuffix = '_' . $timeStamp;
323
            } else {
324
                $namePrefix = $timeStamp . '_';
325
            }
326
        }
327
328
        if ((($fileName = $input->getArgument('filename')) === null || ($isDir = is_dir($fileName))) && !$input->getOption('stdout')) {
0 ignored issues
show
This line exceeds maximum limit of 120 characters; contains 135 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
329
            /** @var DialogHelper $dialog */
330
            $dialog      = $this->getHelperSet()->get('dialog');
331
            $defaultName = $namePrefix . $this->dbSettings['dbname'] . $nameSuffix . $nameExtension;
332
            if (isset($isDir) && $isDir) {
333
                $defaultName = rtrim($fileName, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $defaultName;
334
            }
335
            if (!$input->getOption('force')) {
336
                $fileName = $dialog->ask($output, '<question>Filename for SQL dump:</question> [<comment>'
337
                    . $defaultName . '</comment>]', $defaultName
338
                );
339
            } else {
340
                $fileName = $defaultName;
341
            }
342
        } else {
343
            if ($input->getOption('add-time')) {
344
                $pathParts = pathinfo($fileName);
345
                $fileName = ($pathParts['dirname'] == '.' ? '' : $pathParts['dirname'] . DIRECTORY_SEPARATOR ) .
346
                    $namePrefix . $pathParts['filename'] . $nameSuffix . '.' . $pathParts['extension'];
347
            }
348
        }
349
350
        $fileName = $compressor->getFileName($fileName);
351
352
        return $fileName;
353
    }
354
}
355