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) { |
|
|
|
|
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(); |
|
|
|
|
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() |
|
|
|
|
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
|
|
|
|
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.