1 | <?php |
||||
2 | |||||
3 | namespace Kaliop\eZMigrationBundle\Command; |
||||
4 | |||||
5 | use Kaliop\eZMigrationBundle\API\Value\MigrationDefinition; |
||||
6 | use Kaliop\eZMigrationBundle\API\Value\Migration; |
||||
7 | use Kaliop\eZMigrationBundle\API\Exception\AfterMigrationExecutionException; |
||||
8 | use Kaliop\eZMigrationBundle\API\Exception\MigrationBundleException; |
||||
9 | use Kaliop\eZMigrationBundle\Core\MigrationService; |
||||
10 | use Kaliop\eZMigrationBundle\Core\Process\Process; |
||||
11 | use Kaliop\eZMigrationBundle\Core\Process\ProcessBuilder; |
||||
12 | use Symfony\Component\Console\Input\ArrayInput; |
||||
13 | use Symfony\Component\Console\Input\InputInterface; |
||||
14 | use Symfony\Component\Console\Input\InputOption; |
||||
15 | use Symfony\Component\Console\Output\OutputInterface; |
||||
16 | use Symfony\Component\Console\Helper\Table; |
||||
17 | use Symfony\Component\Console\Question\ConfirmationQuestion; |
||||
18 | use Symfony\Component\Process\PhpExecutableFinder; |
||||
19 | |||||
20 | /** |
||||
21 | * Command to execute the available migration definitions. |
||||
22 | */ |
||||
23 | class MigrateCommand extends AbstractCommand |
||||
24 | { |
||||
25 | // in between QUIET and NORMAL. Sadly the OutputInterface consts changed somewhere between Symfony 2 and 3 |
||||
26 | static $VERBOSITY_CHILD = 0.5; |
||||
27 | |||||
28 | protected $subProcessTimeout = 86400; |
||||
29 | protected $subProcessErrorString = ''; |
||||
30 | |||||
31 | const COMMAND_NAME = 'kaliop:migration:migrate'; |
||||
32 | |||||
33 | /** |
||||
34 | * Set up the command. |
||||
35 | * |
||||
36 | * Define the name, options and help text. |
||||
37 | */ |
||||
38 | 148 | protected function configure() |
|||
39 | { |
||||
40 | 148 | parent::configure(); |
|||
41 | |||||
42 | $this |
||||
43 | 148 | ->setName(self::COMMAND_NAME) |
|||
44 | 148 | ->setAliases(array('kaliop:migration:update')) |
|||
45 | 148 | ->setDescription('Execute available migration definitions.') |
|||
46 | // nb: when adding options, remember to forward them to sub-commands executed in 'separate-process' mode |
||||
47 | 148 | ->addOption('admin-login', 'a', InputOption::VALUE_REQUIRED, "Login of admin account used whenever elevated privileges are needed (user id 14 used by default)") |
|||
48 | 148 | ->addOption('clear-cache', 'c', InputOption::VALUE_NONE, "Clear the cache after the command finishes") |
|||
49 | 148 | ->addOption('default-language', 'l', InputOption::VALUE_REQUIRED, "Default language code that will be used if no language is provided in migration steps") |
|||
50 | 148 | ->addOption('force', 'f', InputOption::VALUE_NONE, "Force (re)execution of migrations already DONE, SKIPPED or FAILED. Use with great care!") |
|||
51 | 148 | ->addOption('ignore-failures', 'i', InputOption::VALUE_NONE, "Keep executing migrations even if one fails") |
|||
52 | 148 | ->addOption('no-interaction', 'n', InputOption::VALUE_NONE, "Do not ask any interactive question") |
|||
53 | 148 | ->addOption('no-transactions', 'u', InputOption::VALUE_NONE, "Do not use a repository transaction to wrap each migration. Unsafe, but needed for legacy slot handlers") |
|||
54 | 148 | ->addOption('path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, "The directory or file to load the migration definitions from") |
|||
55 | 148 | ->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") |
|||
56 | 148 | ->addOption('force-sigchild-enabled', null, InputOption::VALUE_NONE, "When using a separate php process to run each migration, tell Symfony that php was compiled with --enable-sigchild option") |
|||
57 | 148 | ->addOption('survive-disconnected-tty', null, InputOption::VALUE_NONE, "Keep on executing migrations even if the tty where output is written to gets removed. Useful if you run the command over an unstable ssh connection") |
|||
58 | 148 | ->addOption('set-reference', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, "Inject references into the migrations. Format: --set-reference refname:value --set-reference ref2name:value2") |
|||
59 | 148 | ->addOption('child-process-php-ini-config', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, "Passed using `-d` to child php processes when using separate-process. Format: --child-processes-php-ini-config memory_limit:-1 --child-processes-php-ini-config setting2:value2") |
|||
60 | 148 | ->addOption('child', null, InputOption::VALUE_NONE, "*DO NOT USE* Internal option for when forking separate processes") |
|||
61 | 148 | ->setHelp(<<<EOT |
|||
62 | 148 | The <info>kaliop:migration:migrate</info> command loads and executes migrations: |
|||
63 | |||||
64 | <info>php bin/console kaliop:migration:migrate</info> |
||||
65 | |||||
66 | You can optionally specify the path to migration definitions with <info>--path</info>: |
||||
67 | |||||
68 | <info>php bin/console kaliop:migration:migrate --path=/path/to/bundle/version_directory --path=/path/to/bundle/version_directory/single_migration_file</info> |
||||
69 | |||||
70 | Use -v and -vv options to get troubleshooting information on the execution of each step in the migration(s). |
||||
71 | EOT |
||||
72 | ); |
||||
73 | |||||
74 | 148 | if (self::$VERBOSITY_CHILD <= OutputInterface::VERBOSITY_QUIET) { |
|||
75 | 1 | self::$VERBOSITY_CHILD = (OutputInterface::VERBOSITY_QUIET + OutputInterface::VERBOSITY_NORMAL) / 2; |
|||
76 | } |
||||
77 | 148 | } |
|||
78 | |||||
79 | /** |
||||
80 | * Execute the command. |
||||
81 | * |
||||
82 | * @param InputInterface $input |
||||
83 | * @param OutputInterface $output |
||||
84 | * @return null|int null or 0 if everything went fine, or an error code |
||||
85 | */ |
||||
86 | 98 | protected function execute(InputInterface $input, OutputInterface $output) |
|||
87 | { |
||||
88 | 98 | $start = microtime(true); |
|||
89 | |||||
90 | 98 | $this->setOutput($output); |
|||
91 | 98 | $this->setVerbosity($output->getVerbosity()); |
|||
92 | |||||
93 | 98 | if ($input->getOption('child') && $output->getVerbosity() <= OutputInterface::VERBOSITY_NORMAL) { |
|||
94 | $this->setVerbosity(self::$VERBOSITY_CHILD); |
||||
95 | } |
||||
96 | |||||
97 | 98 | $this->getContainer()->get('ez_migration_bundle.step_executed_listener.tracing')->setOutput($output); |
|||
98 | |||||
99 | 98 | $migrationService = $this->getMigrationService(); |
|||
100 | 98 | $migrationService->setOutput($output); |
|||
101 | |||||
102 | 98 | $force = $input->getOption('force'); |
|||
103 | |||||
104 | 98 | $toExecute = $this->buildMigrationsList($this->normalizePaths($input->getOption('path')), $migrationService, $force); |
|||
105 | |||||
106 | 98 | if (!count($toExecute)) { |
|||
107 | 1 | $output->writeln('<info>No migrations to execute</info>'); |
|||
108 | 1 | return 0; |
|||
109 | } |
||||
110 | |||||
111 | 97 | $this->printMigrationsList($toExecute, $input, $output); |
|||
112 | |||||
113 | 97 | if (!$input->getOption('child')) { |
|||
114 | // ask user for confirmation to make changes |
||||
115 | 97 | if (!$this->askForConfirmation($input, $output)) { |
|||
116 | return 0; |
||||
117 | } |
||||
118 | } |
||||
119 | |||||
120 | 97 | if ($input->getOption('separate-process')) { |
|||
121 | 5 | $builder = new ProcessBuilder(); |
|||
122 | 5 | $executableFinder = new PhpExecutableFinder(); |
|||
123 | 5 | if (false !== $php = $executableFinder->find()) { |
|||
124 | 5 | $prefix = array($php); |
|||
125 | |||||
126 | 5 | if ($input->getOption('child-process-php-ini-config')) { |
|||
127 | foreach ($input->getOption('child-process-php-ini-config') as $iniSpec) { |
||||
128 | $ini = explode(':', $iniSpec, 2); |
||||
129 | if (count($ini) < 2 || $ini[0] === '') { |
||||
130 | throw new \InvalidArgumentException("Invalid php ini specification: '$iniSpec'"); |
||||
131 | } |
||||
132 | $prefix[] = '-d ' . $ini[0] . '=' . $ini[1]; |
||||
133 | } |
||||
134 | } |
||||
135 | |||||
136 | 5 | $builder->setPrefix($prefix); |
|||
137 | } |
||||
138 | 5 | $builderArgs = $this->createChildProcessArgs($input); |
|||
139 | } else { |
||||
140 | $forcedRefs = array(); |
||||
141 | if ($input->getOption('set-reference')) { |
||||
142 | 97 | foreach ($input->getOption('set-reference') as $refSpec) { |
|||
143 | 1 | $ref = explode(':', $refSpec, 2); |
|||
144 | if (count($ref) < 2 || $ref[0] === '') { |
||||
145 | throw new \InvalidArgumentException("Invalid reference specification: '$refSpec'"); |
||||
146 | } |
||||
147 | 97 | $forcedRefs[$ref[0]] = $ref[1]; |
|||
148 | 1 | } |
|||
149 | } |
||||
150 | $migrationContext = array( |
||||
151 | 97 | 'useTransaction' => !$input->getOption('no-transactions'), |
|||
152 | 2 | 'defaultLanguageCode' => $input->getOption('default-language'), |
|||
153 | 2 | 'adminUserLogin' => $input->getOption('admin-login'), |
|||
154 | 2 | 'forceExecution' => $force, |
|||
155 | 2 | 'forcedReferences' => $forcedRefs |
|||
156 | ); |
||||
157 | } |
||||
158 | 2 | ||||
159 | // For cli scripts, this means: do not die if anyone yanks out our stdout. |
||||
160 | if ($input->getOption('survive-disconnected-tty')) { |
||||
161 | ignore_user_abort(true); |
||||
162 | 97 | } |
|||
163 | 97 | ||||
164 | 97 | // Allow forcing handling of sigchild. Useful on eg. Debian and Ubuntu |
|||
165 | 97 | if ($input->getOption('force-sigchild-enabled')) { |
|||
166 | 97 | Process::forceSigchildEnabled(true); |
|||
167 | } |
||||
168 | |||||
169 | 97 | $aborted = false; |
|||
170 | $executed = 0; |
||||
171 | $failed = 0; |
||||
172 | $skipped = 0; |
||||
173 | 97 | $total = count($toExecute); |
|||
174 | 7 | ||||
175 | 7 | foreach ($toExecute as $name => $migrationDefinition) { |
|||
176 | 7 | ||||
177 | // let's skip migrations that we know are invalid - user was warned and he decided to proceed anyway |
||||
178 | /// @todo should we save their 'skipped' status in the db? |
||||
179 | 90 | if ($migrationDefinition->status == MigrationDefinition::STATUS_INVALID) { |
|||
180 | 9 | $output->writeln("<comment>Skipping $name</comment>"); |
|||
181 | $skipped++; |
||||
182 | 81 | continue; |
|||
183 | } |
||||
184 | |||||
185 | 90 | if ($this->verbosity >= OutputInterface::VERBOSITY_VERBOSE) { |
|||
186 | $this->writeln("<info>Processing $name (from definition $migrationDefinition->path)</info>"); |
||||
187 | } else { |
||||
188 | 5 | $this->writeln("<info>Processing $name</info>"); |
|||
189 | } |
||||
190 | 5 | ||||
191 | if ($input->getOption('separate-process')) { |
||||
192 | |||||
193 | try { |
||||
194 | $this->executeMigrationInSeparateProcess($migrationDefinition, $migrationService, $builder, $builderArgs); |
||||
195 | |||||
196 | $executed++; |
||||
197 | } catch (\Exception $e) { |
||||
198 | $failed++; |
||||
199 | |||||
200 | $errorMessage = $e->getMessage(); |
||||
201 | // we probably have already echoed the error message while the subprocess was executing, avoid repeating it |
||||
202 | if ($errorMessage != $this->subProcessErrorString) { |
||||
203 | /// @todo atm this is impossible case - executeMigrationInSeparateProcess does not know enough |
||||
204 | /// to throw an AfterMigrationExecutionException |
||||
205 | if ($e instanceof AfterMigrationExecutionException) { |
||||
206 | $errorMessage = "Failure after migration end! Reason: " . $errorMessage; |
||||
207 | } else { |
||||
208 | 5 | $errorMessage = "Migration failed! Reason: " . $errorMessage; |
|||
209 | } |
||||
210 | |||||
211 | $this->writeErrorln("\n<error>$errorMessage</error>"); |
||||
212 | } |
||||
213 | |||||
214 | if (!$input->getOption('ignore-failures')) { |
||||
215 | 85 | $aborted = true; |
|||
216 | break; |
||||
217 | 57 | } |
|||
218 | 28 | } |
|||
219 | 28 | ||||
220 | } else { |
||||
221 | 28 | ||||
222 | try { |
||||
223 | 28 | $this->executeMigrationInProcess($migrationDefinition, $migrationService, $migrationContext); |
|||
224 | 28 | ||||
225 | 28 | $executed++; |
|||
226 | |||||
227 | // in case the 1st mig changes values to the refs, we avoid injecting them in the 2nd mig and later |
||||
228 | $migrationContext['forcedReferences'] = array(); |
||||
229 | } catch (\Exception $e) { |
||||
230 | $failed++; |
||||
231 | |||||
232 | 97 | $errorMessage = $e->getMessage(); |
|||
233 | if ($e instanceof AfterMigrationExecutionException) { |
||||
234 | 97 | $errorMessage = "Failure after migration end! Reason: " . $errorMessage; |
|||
235 | 28 | } else { |
|||
236 | 28 | $errorMessage = "Migration failed! Reason: " . $errorMessage; |
|||
237 | } |
||||
238 | $this->writeErrorln("\n<error>$errorMessage</error>"); |
||||
239 | |||||
240 | if (!$input->getOption('ignore-failures')) { |
||||
241 | 69 | $aborted = true; |
|||
242 | break; |
||||
243 | } |
||||
244 | } |
||||
245 | |||||
246 | } |
||||
247 | } |
||||
248 | 97 | ||||
249 | $missed = $total - $executed - $failed - $skipped; |
||||
250 | 97 | ||||
251 | 97 | if ($aborted) { |
|||
252 | if ($missed > 0) { |
||||
253 | 5 | $this->writeErrorln("\n<error>Migration execution aborted</error>"); |
|||
254 | } |
||||
255 | 92 | } else { |
|||
256 | // NB: as per the Sf doc at https://symfony.com/doc/2.7/console/calling_commands.html, the 'cache:clear' |
||||
257 | // command should be run 'at the end', as they change some class definitions |
||||
258 | 97 | if ($input->getOption('clear-cache')) { |
|||
259 | $command = $this->getApplication()->find('cache:clear'); |
||||
260 | $inputArray = new ArrayInput(array('command' => 'cache:clear')); |
||||
261 | $command->run($inputArray, $output); |
||||
262 | } |
||||
263 | } |
||||
264 | |||||
265 | $this->writeln("\nExecuted $executed migrations, failed $failed, skipped $skipped" . ($missed ? ", missed $missed" : '')); |
||||
266 | |||||
267 | 85 | $time = microtime(true) - $start; |
|||
268 | if ($input->getOption('separate-process')) { |
||||
269 | 85 | // in case of using subprocesses, we can not measure max memory used |
|||
270 | 85 | $this->writeln("<info>Time taken: ".sprintf('%.3f', $time)." secs</info>"); |
|||
271 | 85 | } else { |
|||
272 | 85 | $this->writeln("<info>Time taken: ".sprintf('%.3f', $time)." secs, memory: ".sprintf('%.2f', (memory_get_peak_usage(true) / 1000000)). ' MB</info>'); |
|||
273 | 85 | } |
|||
274 | |||||
275 | return $failed; |
||||
276 | 57 | } |
|||
277 | |||||
278 | /** |
||||
279 | * @param MigrationDefinition $migrationDefinition |
||||
280 | * @param MigrationService $migrationService |
||||
281 | * @param array $migrationContext |
||||
282 | */ |
||||
283 | protected function executeMigrationInProcess($migrationDefinition, $migrationService, $migrationContext) |
||||
284 | { |
||||
285 | 5 | $migrationService->executeMigration( |
|||
286 | $migrationDefinition, |
||||
287 | $migrationContext |
||||
288 | 5 | ); |
|||
289 | 5 | } |
|||
290 | |||||
291 | 5 | /** |
|||
292 | 5 | * @param MigrationDefinition $migrationDefinition |
|||
293 | * @param MigrationService $migrationService |
||||
294 | * @param ProcessBuilder $builder |
||||
295 | 5 | * @param array $builderArgs |
|||
296 | * @param bool $feedback |
||||
297 | */ |
||||
298 | 5 | protected function executeMigrationInSeparateProcess($migrationDefinition, $migrationService, $builder, $builderArgs, $feedback = true) |
|||
299 | { |
||||
300 | $process = $builder |
||||
301 | ->setArguments(array_merge($builderArgs, array('--path=' . $migrationDefinition->path))) |
||||
302 | ->getProcess(); |
||||
303 | |||||
304 | if ($feedback) { |
||||
305 | 5 | $this->writeln('<info>Executing: ' . $process->getCommandLine() . '</info>', OutputInterface::VERBOSITY_NORMAL); |
|||
306 | 5 | } |
|||
307 | |||||
308 | 4 | $this->subProcessErrorString = ''; |
|||
309 | 1 | ||||
310 | 1 | // allow long migrations processes by default |
|||
311 | $process->setTimeout($this->subProcessTimeout); |
||||
312 | |||||
313 | 4 | // and give immediate feedback to the user... |
|||
314 | // NB: if the subprocess writes to stderr then terminates with non-0 exit code, this will lead us to echoing the |
||||
315 | 5 | // error text twice, once here and once at the end of execution of this command. |
|||
316 | // In order to avoid that, since we can not know at this time what the subprocess exit code will be, we |
||||
317 | // do print the error text now, and compare it to what we get at the end... |
||||
318 | 5 | $process->run( |
|||
319 | $feedback ? |
||||
320 | function($type, $buffer) { |
||||
321 | 5 | if ($type == 'err') { |
|||
322 | $this->subProcessErrorString .= $buffer; |
||||
323 | $this->writeErrorln($buffer, OutputInterface::VERBOSITY_QUIET, OutputInterface::OUTPUT_RAW); |
||||
324 | } else { |
||||
325 | // swallow output of child processes in quiet mode |
||||
326 | $this->writeLn($buffer, self::$VERBOSITY_CHILD, OutputInterface::OUTPUT_RAW); |
||||
327 | } |
||||
328 | } |
||||
329 | : |
||||
330 | function($type, $buffer) { |
||||
0 ignored issues
–
show
The parameter
$type is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||
331 | } |
||||
332 | ); |
||||
333 | |||||
334 | if (!$process->isSuccessful()) { |
||||
335 | $errorOutput = $process->getErrorOutput(); |
||||
336 | /// @todo should we always add the exit code to the error message, even when $errorOutput is not null ? |
||||
337 | if ($errorOutput === '') { |
||||
338 | $errorOutput = "(separate process used to execute migration failed with no stderr output. Its exit code was: " . $process->getExitCode(); |
||||
339 | // We go out of our way to help the user finding the cause of the error. |
||||
340 | /// @todo another cause we might check for in case of empty $errorOutput is when error_reporting does |
||||
341 | /// not include fatal errors (E_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR?) |
||||
342 | $errorLog = ini_get("error_log"); |
||||
343 | if ($errorLog != '' && $errorLog != 'syslog') { |
||||
344 | 5 | $errorOutput .= ". Error details might be in file $errorLog"; |
|||
345 | } |
||||
346 | 5 | if ($process->getExitCode() == -1) { |
|||
347 | $errorOutput .= ". If you are using Debian or Ubuntu linux, please consider using the --force-sigchild-enabled option."; |
||||
348 | } |
||||
349 | 5 | $errorOutput .= ")"; |
|||
350 | } |
||||
351 | throw new MigrationBundleException($errorOutput); |
||||
352 | } |
||||
353 | |||||
354 | // There are cases where the separate process dies halfway but does not return a non-zero code. |
||||
355 | // That's why we double-check here if the migration is still tagged as 'started'... |
||||
356 | /** @var Migration $migration */ |
||||
357 | $migration = $migrationService->getMigration($migrationDefinition->name); |
||||
358 | |||||
359 | if (!$migration) { |
||||
360 | // q: shall we add the migration to the db as failed? In doubt, we let it become a ghost, disappeared without a trace... |
||||
361 | 5 | throw new MigrationBundleException("After the separate process charged to execute the migration finished, the migration can not be found in the database any more."); |
|||
362 | } else if ($migration->status == Migration::STATUS_STARTED) { |
||||
363 | $errorMsg = "The separate process charged to execute the migration left it in 'started' state. Most likely it died halfway through execution."; |
||||
364 | $migrationService->endMigration(New Migration( |
||||
365 | $migration->name, |
||||
366 | $migration->md5, |
||||
367 | $migration->path, |
||||
368 | $migration->executionDate, |
||||
369 | Migration::STATUS_FAILED, |
||||
370 | ($migration->executionError != '' ? ($errorMsg . ' ' . $migration->executionError) : $errorMsg) |
||||
371 | 98 | )); |
|||
372 | throw new MigrationBundleException($errorMsg); |
||||
373 | 98 | } |
|||
374 | 98 | } |
|||
375 | |||||
376 | 98 | /** |
|||
377 | 98 | * @param string[] $paths |
|||
378 | 2 | * @param MigrationService $migrationService |
|||
379 | * @param bool $force when true, look not only for TODO migrations, but also DONE, SKIPPED, FAILED ones (we still omit STARTED and SUSPENDED ones) |
||||
380 | * @return MigrationDefinition[] |
||||
381 | * |
||||
382 | 98 | * @todo optimize. This does not scale well with many definitions or migrations |
|||
383 | 98 | */ |
|||
384 | 98 | protected function buildMigrationsList($paths, $migrationService, $force = false) |
|||
385 | 97 | { |
|||
386 | $migrationDefinitions = $migrationService->getMigrationsDefinitions($paths); |
||||
387 | $migrations = $migrationService->getMigrations(); |
||||
388 | |||||
389 | $allowedStatuses = array(Migration::STATUS_TODO); |
||||
390 | if ($force) { |
||||
391 | 98 | $allowedStatuses = array_merge($allowedStatuses, array(Migration::STATUS_DONE, Migration::STATUS_FAILED, Migration::STATUS_SKIPPED)); |
|||
392 | } |
||||
393 | |||||
394 | // filter away all migrations except 'to do' ones |
||||
395 | $toExecute = array(); |
||||
396 | foreach ($migrationDefinitions as $name => $migrationDefinition) { |
||||
397 | if (!isset($migrations[$name]) || (($migration = $migrations[$name]) && in_array($migration->status, $allowedStatuses))) { |
||||
398 | $toExecute[$name] = $migrationService->parseMigrationDefinition($migrationDefinition); |
||||
399 | } |
||||
400 | } |
||||
401 | |||||
402 | // if user wants to execute 'all' migrations: look for those which are registered in the database even if not |
||||
403 | // found by the loader |
||||
404 | if (empty($paths)) { |
||||
405 | foreach ($migrations as $migration) { |
||||
406 | if (in_array($migration->status, $allowedStatuses) && !isset($toExecute[$migration->name])) { |
||||
407 | try { |
||||
408 | $migrationDefinitions = $migrationService->getMigrationsDefinitions(array($migration->path)); |
||||
409 | if (count($migrationDefinitions)) { |
||||
410 | 98 | // q: shall we raise a warning here if migrations found > 1? |
|||
411 | $migrationDefinition = $migrationDefinitions->reset(); |
||||
412 | 98 | $toExecute[$migration->name] = $migrationService->parseMigrationDefinition($migrationDefinition); |
|||
413 | } else { |
||||
414 | throw new MigrationBundleException("Migration definition not found at path '$migration->path'"); |
||||
415 | } |
||||
416 | } catch (\Exception $e) { |
||||
417 | $this->writeErrorln("Error while loading definition for migration '{$migration->name}' registered in the database, skipping it: " . $e->getMessage()); |
||||
418 | } |
||||
419 | } |
||||
420 | } |
||||
421 | } |
||||
422 | 97 | ||||
423 | ksort($toExecute); |
||||
424 | 97 | ||||
425 | 97 | return $toExecute; |
|||
426 | 97 | } |
|||
427 | 97 | ||||
428 | 97 | /** |
|||
429 | 7 | * @param MigrationDefinition[] $toExecute |
|||
430 | * @param InputInterface $input |
||||
431 | 97 | * @param OutputInterface $output |
|||
432 | 97 | * |
|||
433 | 97 | * @todo use a more compact output when there are *many* migrations |
|||
434 | 97 | */ |
|||
435 | protected function printMigrationsList($toExecute, InputInterface $input, OutputInterface $output) |
||||
436 | { |
||||
437 | $data = array(); |
||||
438 | 97 | $i = 1; |
|||
439 | 97 | foreach ($toExecute as $name => $migrationDefinition) { |
|||
440 | $notes = ''; |
||||
441 | 97 | if ($migrationDefinition->status != MigrationDefinition::STATUS_PARSED) { |
|||
442 | 97 | $notes = '<error>' . $migrationDefinition->parsingError . '</error>'; |
|||
443 | 97 | } |
|||
444 | $data[] = array( |
||||
445 | $i++, |
||||
446 | 97 | $name, |
|||
447 | 97 | $notes |
|||
448 | ); |
||||
449 | 97 | } |
|||
450 | |||||
451 | 97 | if (!$input->getOption('child')) { |
|||
452 | $table = new Table($output); |
||||
453 | $table |
||||
454 | ->setHeaders(array('#', 'Migration', 'Notes')) |
||||
455 | ->setRows($data); |
||||
456 | $table->render(); |
||||
457 | } |
||||
458 | |||||
459 | $this->writeln(''); |
||||
460 | } |
||||
461 | |||||
462 | protected function askForConfirmation(InputInterface $input, OutputInterface $output, $nonIteractiveOutput = "=============================================\n") |
||||
463 | 97 | { |
|||
464 | 97 | if ($input->isInteractive() && !$input->getOption('no-interaction')) { |
|||
465 | $dialog = $this->getHelperSet()->get('question'); |
||||
466 | if (!$dialog->ask( |
||||
467 | $input, |
||||
468 | 97 | $output, |
|||
469 | new ConfirmationQuestion('<question>Careful, the database will be modified. Do you want to continue Y/N ?</question>', false) |
||||
470 | ) |
||||
471 | ) { |
||||
472 | $output->writeln('<error>Migration execution cancelled!</error>'); |
||||
473 | return 0; |
||||
474 | } |
||||
475 | } else { |
||||
476 | if ($nonIteractiveOutput != '') { |
||||
477 | $this->writeln("$nonIteractiveOutput"); |
||||
478 | 5 | } |
|||
479 | } |
||||
480 | 5 | ||||
481 | return 1; |
||||
482 | } |
||||
483 | |||||
484 | 5 | /** |
|||
485 | 5 | * Returns the command-line arguments needed to execute a migration in a separate subprocess |
|||
486 | 5 | * (except path, which should be added after this call) |
|||
487 | 5 | * @param InputInterface $input |
|||
488 | * @return array |
||||
489 | * @todo check if it is a good idea to pass on the current verbosity |
||||
490 | 5 | */ |
|||
491 | 5 | protected function createChildProcessArgs(InputInterface $input) |
|||
492 | { |
||||
493 | 5 | $kernel = $this->getContainer()->get('kernel'); |
|||
494 | |||||
495 | // mandatory args and options |
||||
496 | 5 | $builderArgs = array( |
|||
497 | $this->getConsoleFile(), // sf console |
||||
498 | 1 | self::COMMAND_NAME, // name of sf command. Can we get it from the Application instead of hardcoding? |
|||
499 | 1 | '--env=' . $kernel->getEnvironment(), // sf env |
|||
500 | '--child' |
||||
501 | 1 | ); |
|||
502 | 1 | // sf/ez env options |
|||
503 | if (!$kernel->isDebug()) { |
||||
504 | $builderArgs[] = '--no-debug'; |
||||
505 | } |
||||
506 | if ($input->getOption('siteaccess')) { |
||||
507 | $builderArgs[] = '--siteaccess='.$input->getOption('siteaccess'); |
||||
508 | } |
||||
509 | 5 | switch ($this->verbosity) { |
|||
510 | case OutputInterface::VERBOSITY_VERBOSE: |
||||
511 | $builderArgs[] = '-v'; |
||||
512 | 5 | break; |
|||
513 | case OutputInterface::VERBOSITY_VERY_VERBOSE: |
||||
514 | $builderArgs[] = '-vv'; |
||||
515 | 5 | break; |
|||
516 | case OutputInterface::VERBOSITY_DEBUG: |
||||
517 | $builderArgs[] = '-vvv'; |
||||
518 | break; |
||||
519 | 5 | } |
|||
520 | // 'optional' options |
||||
521 | // note: options 'clear-cache', 'ignore-failures', 'no-interaction', 'path', 'separate-process' and 'survive-disconnected-tty' we never propagate |
||||
522 | 5 | if ($input->getOption('admin-login')) { |
|||
523 | $builderArgs[] = '--admin-login=' . $input->getOption('admin-login'); |
||||
524 | } |
||||
525 | 5 | if ($input->getOption('default-language')) { |
|||
526 | $builderArgs[] = '--default-language=' . $input->getOption('default-language'); |
||||
527 | } |
||||
528 | if ($input->getOption('force')) { |
||||
529 | $builderArgs[] = '--force'; |
||||
530 | 5 | } |
|||
531 | // useful in case the subprocess has a migration step of type process/run |
||||
532 | if ($input->getOption('force-sigchild-enabled')) { |
||||
533 | $builderArgs[] = '--force-sigchild-enabled'; |
||||
534 | } |
||||
535 | if ($input->getOption('no-transactions')) { |
||||
536 | $builderArgs[] = '--no-transactions'; |
||||
537 | } |
||||
538 | 5 | if ($input->getOption('set-reference')) { |
|||
539 | foreach ($input->getOption('set-reference') as $refSpec) { |
||||
540 | 5 | $builderArgs[] = '--set-reference=' . $refSpec; |
|||
541 | 5 | } |
|||
542 | 5 | } |
|||
543 | return $builderArgs; |
||||
544 | } |
||||
545 | 5 | ||||
546 | 5 | /** |
|||
547 | * Returns the file-path of the symfony console in use, based on simple heuristics |
||||
548 | * @return string |
||||
549 | * @todo improve how we look for the console: we could fe. scan all of the files in the kernel dir, or look up the full process info based on its pid |
||||
550 | */ |
||||
551 | protected function getConsoleFile() |
||||
552 | { |
||||
553 | if (strpos($_SERVER['argv'][0], 'phpunit') !== false) { |
||||
554 | $kernelDir = $this->getContainer()->get('kernel')->getRootDir(); |
||||
555 | if (is_file("$kernelDir/console")) { |
||||
556 | return "$kernelDir/console"; |
||||
557 | } |
||||
558 | if (is_file("$kernelDir/../bin/console")) { |
||||
559 | return "$kernelDir/../bin/console"; |
||||
560 | } |
||||
561 | throw new MigrationBundleException("Can not determine the name of the symfony console file in use for running as separate process"); |
||||
562 | } |
||||
563 | |||||
564 | return $_SERVER['argv'][0]; // sf console |
||||
565 | } |
||||
566 | } |
||||
567 |
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.