|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace Kaliop\eZMigrationBundle\Command; |
|
4
|
|
|
|
|
5
|
|
|
use Kaliop\eZMigrationBundle\Core\MigrationService; |
|
6
|
|
|
use Symfony\Component\Console\Input\ArrayInput; |
|
7
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
|
8
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
|
9
|
|
|
use Symfony\Component\Console\Input\InputOption; |
|
10
|
|
|
use Kaliop\eZMigrationBundle\API\Value\MigrationDefinition; |
|
11
|
|
|
use Kaliop\eZMigrationBundle\API\Value\Migration; |
|
12
|
|
|
use Kaliop\eZMigrationBundle\API\Exception\AfterMigrationExecutionException; |
|
13
|
|
|
use Symfony\Component\Process\ProcessBuilder; |
|
14
|
|
|
use Symfony\Component\Process\PhpExecutableFinder; |
|
15
|
|
|
use Symfony\Component\Console\Helper\Table; |
|
16
|
|
|
use Symfony\Component\Console\Question\ConfirmationQuestion; |
|
17
|
|
|
|
|
18
|
|
|
/** |
|
19
|
|
|
* Command to execute the available migration definitions. |
|
20
|
|
|
*/ |
|
21
|
|
|
class MigrateCommand extends AbstractCommand |
|
22
|
|
|
{ |
|
23
|
|
|
// in between QUIET and NORMAL |
|
24
|
|
|
const VERBOSITY_CHILD = 0.5; |
|
25
|
|
|
/** @var OutputInterface $output */ |
|
26
|
|
|
protected $output; |
|
27
|
|
|
protected $verbosity = OutputInterface::VERBOSITY_NORMAL; |
|
28
|
|
|
|
|
29
|
|
|
const COMMAND_NAME = 'kaliop:migration:migrate'; |
|
30
|
|
|
|
|
31
|
|
|
/** |
|
32
|
|
|
* Set up the command. |
|
33
|
|
|
* |
|
34
|
|
|
* Define the name, options and help text. |
|
35
|
|
|
*/ |
|
36
|
76 |
|
protected function configure() |
|
37
|
|
|
{ |
|
38
|
76 |
|
parent::configure(); |
|
39
|
|
|
|
|
40
|
|
|
$this |
|
41
|
76 |
|
->setName(self::COMMAND_NAME) |
|
42
|
76 |
|
->setAliases(array('kaliop:migration:update')) |
|
43
|
76 |
|
->setDescription('Execute available migration definitions.') |
|
44
|
76 |
|
// nb: when adding options, remember to forward them to sub-commands executed in 'separate-process' mode |
|
45
|
|
|
->addOption('admin-login', 'a', InputOption::VALUE_REQUIRED, "Login of admin account used whenever elevated privileges are needed (user id 14 used by default)") |
|
46
|
76 |
|
->addOption('clear-cache', 'c', InputOption::VALUE_NONE, "Clear the cache after the command finishes") |
|
47
|
76 |
|
->addOption('default-language', 'l', InputOption::VALUE_REQUIRED, "Default language code that will be used if no language is provided in migration steps") |
|
48
|
76 |
|
->addOption('force', 'f', InputOption::VALUE_NONE, "Force (re)execution of migrations already DONE, SKIPPED or FAILED. Use with great care!") |
|
49
|
76 |
|
->addOption('ignore-failures', 'i', InputOption::VALUE_NONE, "Keep executing migrations even if one fails") |
|
50
|
76 |
|
->addOption('no-interaction', 'n', InputOption::VALUE_NONE, "Do not ask any interactive question") |
|
51
|
76 |
|
->addOption('no-transactions', 'u', InputOption::VALUE_NONE, "Do not use a repository transaction to wrap each migration. Unsafe, but needed for legacy slot handlers") |
|
52
|
76 |
|
->addOption('path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, "The directory or file to load the migration definitions from") |
|
53
|
76 |
|
->addOption('separate-process', 'p', InputOption::VALUE_NONE, "Use a separate php process to run each migration. Safe if your migration leak memory. A tad slower") |
|
54
|
76 |
|
->addOption('child', null, InputOption::VALUE_NONE, "*DO NOT USE* Internal option for when forking separate processes") |
|
55
|
76 |
|
->setHelp(<<<EOT |
|
56
|
|
|
The <info>kaliop:migration:migrate</info> command loads and executes migrations: |
|
57
|
|
|
|
|
58
|
|
|
<info>./ezpublish/console kaliop:migration:migrate</info> |
|
59
|
|
|
|
|
60
|
|
|
You can optionally specify the path to migration definitions with <info>--path</info>: |
|
61
|
|
|
|
|
62
|
|
|
<info>./ezpublish/console kaliop:migrations:migrate --path=/path/to/bundle/version_directory --path=/path/to/bundle/version_directory/single_migration_file</info> |
|
63
|
|
|
EOT |
|
64
|
76 |
|
); |
|
65
|
|
|
} |
|
66
|
|
|
|
|
67
|
|
|
/** |
|
68
|
|
|
* Execute the command. |
|
69
|
|
|
* |
|
70
|
|
|
* @param InputInterface $input |
|
71
|
|
|
* @param OutputInterface $output |
|
72
|
|
|
* @return null|int null or 0 if everything went fine, or an error code |
|
73
|
|
|
*/ |
|
74
|
|
|
protected function execute(InputInterface $input, OutputInterface $output) |
|
75
|
|
|
{ |
|
76
|
|
|
$start = microtime(true); |
|
77
|
|
|
|
|
78
|
|
|
$this->setOutput($output); |
|
79
|
|
|
$this->setVerbosity($output->getVerbosity()); |
|
80
|
|
|
|
|
81
|
|
|
if ($input->getOption('child')) { |
|
82
|
|
|
$this->setVerbosity(self::VERBOSITY_CHILD); |
|
83
|
|
|
} |
|
84
|
|
|
|
|
85
|
|
|
$this->getContainer()->get('ez_migration_bundle.step_executed_listener.tracing')->setOutput($output); |
|
86
|
|
|
|
|
87
|
|
|
$migrationService = $this->getMigrationService(); |
|
88
|
|
|
|
|
89
|
|
|
$force = $input->getOption('force'); |
|
90
|
|
|
|
|
91
|
|
|
$toExecute = $this->buildMigrationsList($input->getOption('path'), $migrationService, $force); |
|
92
|
|
|
|
|
93
|
|
|
if (!count($toExecute)) { |
|
94
|
|
|
$output->writeln('<info>No migrations to execute</info>'); |
|
95
|
|
|
return 0; |
|
96
|
|
|
} |
|
97
|
|
|
|
|
98
|
|
|
$this->printMigrationsList($toExecute, $input, $output); |
|
99
|
|
|
|
|
100
|
|
|
// ask user for confirmation to make changes |
|
101
|
|
|
if (!$this->askForConfirmation($input, $output)) { |
|
102
|
|
|
return 0; |
|
103
|
|
|
} |
|
104
|
|
|
|
|
105
|
|
View Code Duplication |
if ($input->getOption('separate-process')) { |
|
|
|
|
|
|
106
|
|
|
$builder = new ProcessBuilder(); |
|
107
|
|
|
$executableFinder = new PhpExecutableFinder(); |
|
108
|
|
|
if (false !== $php = $executableFinder->find()) { |
|
109
|
|
|
$builder->setPrefix($php); |
|
110
|
|
|
} |
|
111
|
|
|
$builderArgs = $this->createChildProcessArgs($input); |
|
112
|
|
|
} |
|
113
|
|
|
|
|
114
|
|
|
$executed = 0; |
|
115
|
|
|
$failed = 0; |
|
116
|
|
|
$skipped = 0; |
|
117
|
|
|
|
|
118
|
|
|
/** @var MigrationDefinition $migrationDefinition */ |
|
119
|
|
|
foreach ($toExecute as $name => $migrationDefinition) { |
|
120
|
|
|
|
|
121
|
|
|
// let's skip migrations that we know are invalid - user was warned and he decided to proceed anyway |
|
122
|
|
|
if ($migrationDefinition->status == MigrationDefinition::STATUS_INVALID) { |
|
123
|
|
|
$output->writeln("<comment>Skipping $name</comment>\n"); |
|
124
|
|
|
$skipped++; |
|
125
|
|
|
continue; |
|
126
|
|
|
} |
|
127
|
|
|
|
|
128
|
|
|
$this->writeln("<info>Processing $name</info>"); |
|
129
|
|
|
|
|
130
|
|
|
if ($input->getOption('separate-process')) { |
|
131
|
|
|
|
|
132
|
|
|
try { |
|
133
|
|
|
$this->executeMigrationInSeparateProcess($migrationDefinition, $migrationService, $builder, $builderArgs); |
|
|
|
|
|
|
134
|
|
|
|
|
135
|
|
|
$executed++; |
|
136
|
|
|
} catch (\Exception $e) { |
|
137
|
|
|
if ($input->getOption('ignore-failures')) { |
|
138
|
|
|
$output->writeln("\n<error>Migration failed! Reason: " . $e->getMessage() . "</error>\n"); |
|
139
|
|
|
$failed++; |
|
140
|
|
|
continue; |
|
141
|
|
|
} |
|
142
|
|
|
if ($e instanceof AfterMigrationExecutionException) { |
|
143
|
|
|
$output->writeln("\n<error>Failure after migration end! Reason: " . $e->getMessage() . "</error>"); |
|
144
|
|
|
} else { |
|
145
|
|
|
$output->writeln("\n<error>Migration aborted! Reason: " . $e->getMessage() . "</error>"); |
|
146
|
|
|
} |
|
147
|
|
|
return 1; |
|
148
|
|
|
} |
|
149
|
|
|
|
|
150
|
|
|
} else { |
|
151
|
|
|
|
|
152
|
|
|
try { |
|
153
|
|
|
$this->executeMigrationInProcess($migrationDefinition, $force, $migrationService, $input); |
|
154
|
|
|
|
|
155
|
|
|
$executed++; |
|
156
|
|
|
} catch (\Exception $e) { |
|
157
|
|
|
if ($input->getOption('ignore-failures')) { |
|
158
|
|
|
$output->writeln("\n<error>Migration failed! Reason: " . $e->getMessage() . "</error>\n"); |
|
159
|
|
|
$failed++; |
|
160
|
|
|
continue; |
|
161
|
|
|
} |
|
162
|
|
|
$output->writeln("\n<error>Migration aborted! Reason: " . $e->getMessage() . "</error>"); |
|
163
|
|
|
return 1; |
|
164
|
|
|
} |
|
165
|
|
|
|
|
166
|
|
|
} |
|
167
|
|
|
} |
|
168
|
|
|
|
|
169
|
|
View Code Duplication |
if ($input->getOption('clear-cache')) { |
|
|
|
|
|
|
170
|
|
|
$command = $this->getApplication()->find('cache:clear'); |
|
171
|
|
|
$inputArray = new ArrayInput(array('command' => 'cache:clear')); |
|
172
|
|
|
$command->run($inputArray, $output); |
|
173
|
|
|
} |
|
174
|
|
|
|
|
175
|
|
|
$time = microtime(true) - $start; |
|
176
|
|
|
$this->writeln("Executed $executed migrations, failed $failed, skipped $skipped"); |
|
177
|
|
|
if ($input->getOption('separate-process')) { |
|
178
|
|
|
// in case of using subprocesses, we can not measure max memory used |
|
179
|
|
|
$this->writeln("Time taken: ".sprintf('%.2f', $time)." secs"); |
|
180
|
|
|
} else { |
|
181
|
|
|
$this->writeln("Time taken: ".sprintf('%.2f', $time)." secs, memory: ".sprintf('%.2f', (memory_get_peak_usage(true) / 1000000)). ' MB'); |
|
182
|
|
|
} |
|
183
|
|
|
} |
|
184
|
|
|
|
|
185
|
|
|
protected function executeMigrationInProcess($migrationDefinition, $force, $migrationService, $input) |
|
186
|
|
|
{ |
|
187
|
|
|
$migrationService->executeMigration( |
|
188
|
|
|
$migrationDefinition, |
|
189
|
|
|
!$input->getOption('no-transactions'), |
|
190
|
|
|
$input->getOption('default-language'), |
|
191
|
|
|
$input->getOption('admin-login'), |
|
192
|
|
|
$force |
|
193
|
|
|
); |
|
194
|
|
|
} |
|
195
|
|
|
|
|
196
|
|
|
protected function executeMigrationInSeparateProcess($migrationDefinition, $migrationService, $builder, $builderArgs, $feedback = true) |
|
197
|
|
|
{ |
|
198
|
|
|
$process = $builder |
|
199
|
|
|
->setArguments(array_merge($builderArgs, array('--path=' . $migrationDefinition->path))) |
|
200
|
|
|
->getProcess(); |
|
201
|
|
|
|
|
202
|
|
|
if ($feedback) { |
|
203
|
|
|
$this->writeln('<info>Executing: ' . $process->getCommandLine() . '</info>', OutputInterface::VERBOSITY_VERBOSE); |
|
204
|
|
|
} |
|
205
|
|
|
|
|
206
|
|
|
// allow long migrations processes by default |
|
207
|
|
|
$process->setTimeout(86400); |
|
208
|
|
|
// and give immediate feedback to the user |
|
209
|
|
|
$process->run( |
|
210
|
|
|
$feedback ? |
|
211
|
|
|
function($type, $buffer) { |
|
212
|
|
|
echo $buffer; |
|
213
|
|
|
} |
|
214
|
|
|
: |
|
215
|
|
|
function($type, $buffer) { |
|
|
|
|
|
|
216
|
|
|
} |
|
217
|
|
|
); |
|
218
|
|
|
|
|
219
|
|
|
if (!$process->isSuccessful()) { |
|
220
|
|
|
throw new \Exception($process->getErrorOutput()); |
|
221
|
|
|
} |
|
222
|
|
|
|
|
223
|
|
|
// There are cases where the separate process dies halfway but does not return a non-zero code. |
|
224
|
|
|
// That's why we double-check here if the migration is still tagged as 'started'... |
|
225
|
|
|
/** @var Migration $migration */ |
|
226
|
|
|
$migration = $migrationService->getMigration($migrationDefinition->name); |
|
227
|
|
|
|
|
228
|
|
|
if (!$migration) { |
|
229
|
|
|
// q: shall we add the migration to the db as failed? In doubt, we let it become a ghost, disappeared without a trace... |
|
230
|
|
|
throw new \Exception("After the separate process charged to execute the migration finished, the migration can not be found in the database any more."); |
|
231
|
|
|
} else if ($migration->status == Migration::STATUS_STARTED) { |
|
232
|
|
|
$errorMsg = "The separate process charged to execute the migration left it in 'started' state. Most likely it died halfway through execution."; |
|
233
|
|
|
$migrationService->endMigration(New Migration( |
|
234
|
|
|
$migration->name, |
|
235
|
|
|
$migration->md5, |
|
236
|
|
|
$migration->path, |
|
237
|
|
|
$migration->executionDate, |
|
238
|
|
|
Migration::STATUS_FAILED, |
|
239
|
|
|
($migration->executionError != '' ? ($errorMsg . ' ' . $migration->executionError) : $errorMsg) |
|
240
|
|
|
)); |
|
241
|
|
|
throw new \Exception($errorMsg); |
|
242
|
|
|
} |
|
243
|
|
|
} |
|
244
|
|
|
|
|
245
|
|
|
/** |
|
246
|
|
|
* @param string[] $paths |
|
247
|
|
|
* @param MigrationService $migrationService |
|
248
|
|
|
* @param bool $force when true, look not only for TODO migrations, but also DONE, SKIPPED, FAILED ones (we still omit STARTED and SUSPENDED ones) |
|
249
|
|
|
* @return MigrationDefinition[] |
|
250
|
|
|
* |
|
251
|
|
|
* @todo this does not scale well with many definitions or migrations |
|
252
|
|
|
*/ |
|
253
|
|
|
protected function buildMigrationsList($paths, $migrationService, $force = false) |
|
254
|
|
|
{ |
|
255
|
|
|
$migrationDefinitions = $migrationService->getMigrationsDefinitions($paths); |
|
256
|
|
|
$migrations = $migrationService->getMigrations(); |
|
257
|
|
|
|
|
258
|
|
|
$allowedStatuses = array(Migration::STATUS_TODO); |
|
259
|
|
View Code Duplication |
if ($force) { |
|
|
|
|
|
|
260
|
|
|
$allowedStatuses = array_merge($allowedStatuses, array(Migration::STATUS_DONE, Migration::STATUS_FAILED, Migration::STATUS_SKIPPED)); |
|
261
|
|
|
} |
|
262
|
|
|
|
|
263
|
|
|
// filter away all migrations except 'to do' ones |
|
264
|
|
|
$toExecute = array(); |
|
265
|
|
|
foreach ($migrationDefinitions as $name => $migrationDefinition) { |
|
266
|
|
View Code Duplication |
if (!isset($migrations[$name]) || (($migration = $migrations[$name]) && in_array($migration->status, $allowedStatuses))) { |
|
|
|
|
|
|
267
|
|
|
$toExecute[$name] = $migrationService->parseMigrationDefinition($migrationDefinition); |
|
268
|
|
|
} |
|
269
|
|
|
} |
|
270
|
|
|
|
|
271
|
|
|
// if user wants to execute 'all' migrations: look for some which are registered in the database even if not |
|
272
|
|
|
// found by the loader |
|
273
|
|
View Code Duplication |
if (empty($paths)) { |
|
|
|
|
|
|
274
|
|
|
foreach ($migrations as $migration) { |
|
275
|
|
|
if (in_array($migration->status, $allowedStatuses) && !isset($toExecute[$migration->name])) { |
|
276
|
|
|
$migrationDefinitions = $migrationService->getMigrationsDefinitions(array($migration->path)); |
|
277
|
|
|
if (count($migrationDefinitions)) { |
|
278
|
|
|
$migrationDefinition = reset($migrationDefinitions); |
|
279
|
|
|
$toExecute[$migration->name] = $migrationService->parseMigrationDefinition($migrationDefinition); |
|
280
|
|
|
} else { |
|
281
|
|
|
// q: shall we raise a warning here ? |
|
282
|
|
|
} |
|
283
|
|
|
} |
|
284
|
|
|
} |
|
285
|
|
|
} |
|
286
|
|
|
|
|
287
|
|
|
ksort($toExecute); |
|
288
|
|
|
|
|
289
|
|
|
return $toExecute; |
|
290
|
|
|
} |
|
291
|
|
|
|
|
292
|
|
|
/** |
|
293
|
|
|
* @param MigrationDefinition[] $toExecute |
|
294
|
|
|
* @param InputInterface $input |
|
295
|
|
|
* @param OutputInterface $output |
|
296
|
|
|
* |
|
297
|
|
|
* @todo use a more compact output when there are *many* migrations |
|
298
|
|
|
*/ |
|
299
|
|
|
protected function printMigrationsList($toExecute, InputInterface $input, OutputInterface $output) |
|
300
|
|
|
{ |
|
301
|
|
|
$data = array(); |
|
302
|
|
|
$i = 1; |
|
303
|
|
|
foreach ($toExecute as $name => $migrationDefinition) { |
|
304
|
|
|
$notes = ''; |
|
305
|
|
|
if ($migrationDefinition->status != MigrationDefinition::STATUS_PARSED) { |
|
306
|
|
|
$notes = '<error>' . $migrationDefinition->parsingError . '</error>'; |
|
307
|
|
|
} |
|
308
|
|
|
$data[] = array( |
|
309
|
|
|
$i++, |
|
310
|
|
|
$name, |
|
311
|
|
|
$notes |
|
312
|
|
|
); |
|
313
|
|
|
} |
|
314
|
|
|
|
|
315
|
|
|
if (!$input->getOption('child')) { |
|
316
|
|
|
$table = new Table($output); |
|
317
|
|
|
$table |
|
318
|
|
|
->setHeaders(array('#', 'Migration', 'Notes')) |
|
319
|
|
|
->setRows($data); |
|
320
|
|
|
$table->render(); |
|
321
|
|
|
} |
|
322
|
|
|
|
|
323
|
|
|
$this->writeln(''); |
|
324
|
|
|
} |
|
325
|
|
|
|
|
326
|
|
|
protected function askForConfirmation(InputInterface $input, OutputInterface $output, $nonIteractiveOutput = "=============================================\n") |
|
327
|
|
|
{ |
|
328
|
|
|
if ($input->isInteractive() && !$input->getOption('no-interaction')) { |
|
329
|
|
|
$dialog = $this->getHelperSet()->get('question'); |
|
330
|
|
|
if (!$dialog->ask( |
|
331
|
|
|
$input, |
|
332
|
|
|
$output, |
|
333
|
|
|
new ConfirmationQuestion('<question>Careful, the database will be modified. Do you want to continue Y/N ?</question>', false) |
|
334
|
|
|
) |
|
335
|
|
|
) { |
|
336
|
|
|
$output->writeln('<error>Migration execution cancelled!</error>'); |
|
337
|
|
|
return 0; |
|
338
|
|
|
} |
|
339
|
|
|
} else { |
|
340
|
|
|
if ($nonIteractiveOutput != '') { |
|
341
|
|
|
$this->writeln("$nonIteractiveOutput"); |
|
342
|
|
|
} |
|
343
|
|
|
} |
|
344
|
|
|
|
|
345
|
|
|
return 1; |
|
346
|
|
|
} |
|
347
|
|
|
|
|
348
|
|
|
/** |
|
349
|
|
|
* Small tricks to allow us to lower verbosity between NORMAL and QUIET and have a decent writeln API, even with old SF versions |
|
350
|
|
|
* @param $message |
|
351
|
|
|
* @param int $verbosity |
|
352
|
|
|
*/ |
|
353
|
|
|
protected function writeln($message, $verbosity = OutputInterface::VERBOSITY_NORMAL) |
|
354
|
|
|
{ |
|
355
|
|
|
if ($this->verbosity >= $verbosity) { |
|
356
|
|
|
$this->output->writeln($message); |
|
357
|
|
|
} |
|
358
|
|
|
} |
|
359
|
|
|
|
|
360
|
|
|
protected function setOutput(OutputInterface $output) |
|
361
|
|
|
{ |
|
362
|
|
|
$this->output = $output; |
|
363
|
|
|
} |
|
364
|
|
|
|
|
365
|
|
|
protected function setVerbosity($verbosity) |
|
366
|
|
|
{ |
|
367
|
|
|
$this->verbosity = $verbosity; |
|
368
|
|
|
} |
|
369
|
|
|
|
|
370
|
|
|
/** |
|
371
|
|
|
* Returns the command-line arguments needed to execute a migration in a separate subprocess (omitting 'path') |
|
372
|
|
|
* @param InputInterface $input |
|
373
|
|
|
* @return array |
|
374
|
|
|
*/ |
|
375
|
|
|
protected function createChildProcessArgs(InputInterface $input) |
|
376
|
|
|
{ |
|
377
|
|
|
$kernel = $this->getContainer()->get('kernel'); |
|
378
|
|
|
|
|
379
|
|
|
// mandatory args and options |
|
380
|
|
|
$builderArgs = array( |
|
381
|
|
|
$_SERVER['argv'][0], // sf console |
|
382
|
|
|
self::COMMAND_NAME, // name of sf command. Can we get it from the Application instead of hardcoding? |
|
383
|
|
|
'--env=' . $kernel->getEnvironment(), // sf env |
|
384
|
|
|
'--child' |
|
385
|
|
|
); |
|
386
|
|
|
// sf/ez env options |
|
387
|
|
|
if (!$kernel->isDebug()) { |
|
388
|
|
|
$builderArgs[] = '--no-debug'; |
|
389
|
|
|
} |
|
390
|
|
|
if ($input->getOption('siteaccess')) { |
|
391
|
|
|
$builderArgs[]='--siteaccess='.$input->getOption('siteaccess'); |
|
392
|
|
|
} |
|
393
|
|
|
// 'optional' options |
|
394
|
|
|
// note: options 'clear-cache', 'ignore-failures', 'no-interaction', 'path' and 'separate-process' we never propagate |
|
395
|
|
|
if ($input->getOption('admin-login')) { |
|
396
|
|
|
$builderArgs[] = '--admin-login=' . $input->getOption('admin-login'); |
|
397
|
|
|
} |
|
398
|
|
|
if ($input->getOption('default-language')) { |
|
399
|
|
|
$builderArgs[] = '--default-language=' . $input->getOption('default-language'); |
|
400
|
|
|
} |
|
401
|
|
|
if ($input->getOption('force')) { |
|
402
|
|
|
$builderArgs[] = '--force'; |
|
403
|
|
|
} |
|
404
|
|
|
if ($input->getOption('no-transactions')) { |
|
405
|
|
|
$builderArgs[] = '--no-transactions'; |
|
406
|
|
|
} |
|
407
|
|
|
|
|
408
|
|
|
return $builderArgs; |
|
409
|
|
|
} |
|
410
|
|
|
} |
|
411
|
|
|
|
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.