kaliop-uk /
ezmigrationbundle
| 1 | <?php |
||||
| 2 | |||||
| 3 | namespace Kaliop\eZMigrationBundle\Command; |
||||
| 4 | |||||
| 5 | use Kaliop\eZMigrationBundle\API\Exception\AfterMigrationExecutionException; |
||||
| 6 | use Kaliop\eZMigrationBundle\API\Value\Migration; |
||||
| 7 | use Kaliop\eZMigrationBundle\API\Value\MigrationDefinition; |
||||
| 8 | use Kaliop\eZMigrationBundle\Core\Helper\ProcessManager; |
||||
| 9 | use Kaliop\eZMigrationBundle\Core\Process\Process; |
||||
| 10 | use Kaliop\eZMigrationBundle\Core\Process\ProcessBuilder; |
||||
| 11 | use Symfony\Component\Console\Input\ArrayInput; |
||||
| 12 | use Symfony\Component\Console\Input\InputOption; |
||||
| 13 | use Symfony\Component\Console\Input\InputInterface; |
||||
| 14 | use Symfony\Component\Console\Output\Output; |
||||
| 15 | use Symfony\Component\Console\Output\OutputInterface; |
||||
| 16 | use Symfony\Component\Process\PhpExecutableFinder; |
||||
| 17 | |||||
| 18 | class MassMigrateCommand extends MigrateCommand |
||||
| 19 | { |
||||
| 20 | const COMMAND_NAME = 'kaliop:migration:mass_migrate'; |
||||
| 21 | |||||
| 22 | // Note: in this array, we lump together in STATUS_DONE everything which is not failed or suspended |
||||
| 23 | protected $migrationsDone = array(Migration::STATUS_DONE => 0, Migration::STATUS_FAILED => 0, Migration::STATUS_SKIPPED => 0); |
||||
| 24 | protected $migrationsAlreadyDone = array(); |
||||
| 25 | |||||
| 26 | /** |
||||
| 27 | * @todo (!important) can we rename the option --separate-process ? |
||||
| 28 | */ |
||||
| 29 | protected function configure() |
||||
| 30 | 148 | { |
|||
| 31 | parent::configure(); |
||||
| 32 | 148 | ||||
| 33 | $this |
||||
| 34 | ->setName(self::COMMAND_NAME) |
||||
| 35 | 148 | ->setAliases(array()) |
|||
| 36 | 148 | ->setDescription('Executes available migration definitions, using parallelism.') |
|||
| 37 | 148 | ->addOption('concurrency', 'r', InputOption::VALUE_REQUIRED, "The number of executors to run in parallel", 2) |
|||
| 38 | 148 | ->setHelp(<<<EOT |
|||
| 39 | 148 | This command is designed to scan recursively a directory for migration files and execute them all in parallel. |
|||
| 40 | 148 | One child process will be spawned for each subdirectory found. |
|||
| 41 | The maximum number of processes to run in parallel is specified via the 'concurrency' option. |
||||
| 42 | <info>NB: this command does not guarantee that any given migration will be executed before another. Take care about dependencies.</info> |
||||
| 43 | <info>NB: the rule that each migration filename has to be unique still applies, even if migrations are spread across different directories.</info> |
||||
| 44 | Unlike for the 'normal' migration command, it is not recommended to use the <info>--separate-process</info> option, as it will make execution slower if you have many migrations |
||||
| 45 | EOT |
||||
| 46 | ) |
||||
| 47 | ; |
||||
| 48 | } |
||||
| 49 | 148 | ||||
| 50 | /** |
||||
| 51 | * Execute the command. |
||||
| 52 | * |
||||
| 53 | * @param InputInterface $input |
||||
| 54 | * @param OutputInterface $output |
||||
| 55 | * @return null|int null or 0 if everything went fine, or an error code |
||||
| 56 | */ |
||||
| 57 | protected function execute(InputInterface $input, OutputInterface $output) |
||||
| 58 | { |
||||
| 59 | $start = microtime(true); |
||||
| 60 | |||||
| 61 | $this->setOutput($output); |
||||
| 62 | $this->setVerbosity($output->getVerbosity()); |
||||
| 63 | |||||
| 64 | $isChild = $input->getOption('child'); |
||||
| 65 | |||||
| 66 | if ($isChild && $output->getVerbosity() <= OutputInterface::VERBOSITY_NORMAL) { |
||||
| 67 | $this->setVerbosity(self::$VERBOSITY_CHILD); |
||||
| 68 | } |
||||
| 69 | |||||
| 70 | $this->getContainer()->get('ez_migration_bundle.step_executed_listener.tracing')->setOutput($output); |
||||
| 71 | |||||
| 72 | // q: is it worth declaring a new, dedicated migration service ? |
||||
| 73 | $migrationService = $this->getMigrationService(); |
||||
| 74 | $migrationService->setLoader($this->getContainer()->get('ez_migration_bundle.loader.filesystem_recursive')); |
||||
| 75 | |||||
| 76 | $force = $input->getOption('force'); |
||||
| 77 | |||||
| 78 | $toExecute = $this->buildMigrationsList($this->normalizePaths($input->getOption('path')), $migrationService, $force, $isChild); |
||||
| 79 | |||||
| 80 | if (!count($toExecute)) { |
||||
| 81 | $this->writeln('<info>No migrations to execute</info>'); |
||||
| 82 | return 0; |
||||
| 83 | } |
||||
| 84 | |||||
| 85 | if ($isChild) { |
||||
| 86 | return $this->executeAsChild($input, $output, $toExecute, $force, $migrationService); |
||||
| 87 | } else { |
||||
| 88 | return $this->executeAsParent($input, $output, $toExecute, $start); |
||||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||
| 89 | } |
||||
| 90 | } |
||||
| 91 | |||||
| 92 | /** |
||||
| 93 | * @param InputInterface $input |
||||
| 94 | * @param OutputInterface $output |
||||
| 95 | * @param MigrationDefinition[] $toExecute |
||||
| 96 | * @param float $start |
||||
| 97 | * @return int |
||||
| 98 | */ |
||||
| 99 | protected function executeAsParent($input, $output, $toExecute, $start) |
||||
| 100 | { |
||||
| 101 | $paths = $this->groupMigrationsByPath($toExecute); |
||||
| 102 | $this->printMigrationsList($toExecute, $input, $output, $paths); |
||||
| 103 | |||||
| 104 | // ask user for confirmation to make changes |
||||
| 105 | if (!$this->askForConfirmation($input, $output, null)) { |
||||
| 106 | return 0; |
||||
| 107 | } |
||||
| 108 | |||||
| 109 | // For cli scripts, this means: do not die if anyone yanks out our stdout. |
||||
| 110 | // We presume that users who want to halt migrations do send us a KILL signal, and that a lost tty is |
||||
| 111 | // generally a mistake, and that carrying on with executing migrations is the best outcome |
||||
| 112 | if ($input->getOption('survive-disconnected-tty')) { |
||||
| 113 | ignore_user_abort(true); |
||||
| 114 | } |
||||
| 115 | |||||
| 116 | $concurrency = $input->getOption('concurrency'); |
||||
| 117 | $this->writeln("Executing migrations using " . count($paths) . " processes with a concurrency of $concurrency"); |
||||
| 118 | |||||
| 119 | // Allow forcing handling of sigchild. Useful on eg. Debian and Ubuntu |
||||
| 120 | if ($input->getOption('force-sigchild-enabled')) { |
||||
| 121 | Process::forceSigchildEnabled(true); |
||||
| 122 | } |
||||
| 123 | |||||
| 124 | $builder = new ProcessBuilder(); |
||||
| 125 | $executableFinder = new PhpExecutableFinder(); |
||||
| 126 | if (false !== ($php = $executableFinder->find())) { |
||||
| 127 | $builder->setPrefix($php); |
||||
| 128 | } |
||||
| 129 | |||||
| 130 | // mandatory args and options |
||||
| 131 | $builderArgs = $this->createChildProcessArgs($input); |
||||
| 132 | |||||
| 133 | $processes = array(); |
||||
| 134 | /** @var MigrationDefinition $migrationDefinition */ |
||||
| 135 | foreach ($paths as $path => $count) { |
||||
| 136 | $this->writeln("<info>Queueing processing of: $path ($count migrations)</info>", OutputInterface::VERBOSITY_VERBOSE); |
||||
| 137 | |||||
| 138 | $process = $builder |
||||
| 139 | ->setArguments(array_merge($builderArgs, array('--path=' . $path))) |
||||
| 140 | ->getProcess(); |
||||
| 141 | |||||
| 142 | $this->writeln('<info>Command: ' . $process->getCommandLine() . '</info>', OutputInterface::VERBOSITY_VERBOSE); |
||||
| 143 | |||||
| 144 | // allow long migrations processes by default |
||||
| 145 | $process->setTimeout($this->subProcessTimeout); |
||||
| 146 | $processes[] = $process; |
||||
| 147 | } |
||||
| 148 | |||||
| 149 | $this->writeln("<info>Starting queued processes...</info>"); |
||||
| 150 | |||||
| 151 | $total = count($toExecute); |
||||
| 152 | $this->migrationsDone = array(Migration::STATUS_DONE => 0, Migration::STATUS_FAILED => 0, Migration::STATUS_SKIPPED => 0); |
||||
| 153 | |||||
| 154 | $processManager = new ProcessManager(); |
||||
| 155 | $processManager->runParallel($processes, $concurrency, 500, array($this, 'onChildProcessOutput')); |
||||
| 156 | |||||
| 157 | $subprocessesFailed = 0; |
||||
| 158 | foreach ($processes as $i => $process) { |
||||
| 159 | if (!$process->isSuccessful()) { |
||||
| 160 | $errorOutput = $process->getErrorOutput(); |
||||
| 161 | if ($errorOutput === '') { |
||||
| 162 | $errorOutput = "(process used to execute migrations failed with no stderr output. Its exit code was: " . $process->getExitCode(); |
||||
| 163 | // We go out of our way to help the user finding the cause of the error. |
||||
| 164 | /// @todo another cause we might check for in case of an empty $errorOutput is when error_reporting |
||||
| 165 | /// does not include fatal errors (E_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR?) |
||||
| 166 | $errorLog = ini_get("error_log"); |
||||
| 167 | if ($errorLog != '' && $errorLog != 'syslog') { |
||||
| 168 | $errorOutput .= ". Error details might be in file $errorLog"; |
||||
| 169 | } |
||||
| 170 | if ($process->getExitCode() == -1) { |
||||
| 171 | $errorOutput .= ". If you are using Debian or Ubuntu linux, please consider using the --force-sigchild-enabled option."; |
||||
| 172 | } |
||||
| 173 | $errorOutput .= ")"; |
||||
| 174 | } |
||||
| 175 | /// @todo should we always add the exit code, even when $errorOutput is not null ? |
||||
| 176 | $this->writeErrorln("\n<error>Subprocess $i failed! Reason: " . $errorOutput . "</error>\n"); |
||||
| 177 | $subprocessesFailed++; |
||||
| 178 | } |
||||
| 179 | } |
||||
| 180 | |||||
| 181 | if ($input->getOption('clear-cache')) { |
||||
| 182 | /// @see the comment in the parent class about the problems tied to clearing Sf cache in-process |
||||
| 183 | $command = $this->getApplication()->find('cache:clear'); |
||||
| 184 | $inputArray = new ArrayInput(array('command' => 'cache:clear')); |
||||
| 185 | $command->run($inputArray, $output); |
||||
| 186 | } |
||||
| 187 | |||||
| 188 | $missed = $total - $this->migrationsDone[Migration::STATUS_DONE] - $this->migrationsDone[Migration::STATUS_FAILED] - $this->migrationsDone[Migration::STATUS_SKIPPED]; |
||||
| 189 | $this->writeln("\nExecuted ".$this->migrationsDone[Migration::STATUS_DONE].' migrations'. |
||||
| 190 | ', failed '.$this->migrationsDone[Migration::STATUS_FAILED]. |
||||
| 191 | ', skipped '.$this->migrationsDone[Migration::STATUS_SKIPPED]. |
||||
| 192 | ($missed ? ", missed $missed" : '')); |
||||
| 193 | |||||
| 194 | $time = microtime(true) - $start; |
||||
| 195 | // since we use subprocesses, we can not measure max memory used |
||||
| 196 | $this->writeln("<info>Time taken: ".sprintf('%.3f', $time)." secs</info>"); |
||||
| 197 | |||||
| 198 | return $subprocessesFailed + $this->migrationsDone[Migration::STATUS_FAILED] + $missed; |
||||
| 199 | } |
||||
| 200 | |||||
| 201 | /** |
||||
| 202 | * @param InputInterface $input |
||||
| 203 | * @param OutputInterface $output |
||||
| 204 | * @param MigrationDefinition[] $toExecute |
||||
| 205 | * @param bool $force |
||||
| 206 | * @param $migrationService |
||||
| 207 | * @return int |
||||
| 208 | * @todo does it make sense to honour the `survive-disconnected-tty` flag when executing as child? |
||||
| 209 | */ |
||||
| 210 | protected function executeAsChild($input, $output, $toExecute, $force, $migrationService) |
||||
|
0 ignored issues
–
show
The parameter
$output 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. Loading history...
|
|||||
| 211 | { |
||||
| 212 | // @todo disable signal slots that are harmful during migrations, if any |
||||
| 213 | |||||
| 214 | if ($input->getOption('separate-process')) { |
||||
| 215 | $builder = new ProcessBuilder(); |
||||
| 216 | $executableFinder = new PhpExecutableFinder(); |
||||
| 217 | if (false !== $php = $executableFinder->find()) { |
||||
| 218 | $prefix = array($php); |
||||
| 219 | |||||
| 220 | if ($input->getOption('child-process-php-ini-config')) { |
||||
| 221 | foreach ($input->getOption('child-process-php-ini-config') as $iniSpec) { |
||||
| 222 | $ini = explode(':', $iniSpec, 2); |
||||
| 223 | if (count($ini) < 2 || $ini[0] === '') { |
||||
| 224 | throw new \InvalidArgumentException("Invalid php ini specification: '$iniSpec'"); |
||||
| 225 | } |
||||
| 226 | $prefix[] = '-d ' . $ini[0] . '=' . $ini[1]; |
||||
| 227 | } |
||||
| 228 | } |
||||
| 229 | |||||
| 230 | $builder->setPrefix($prefix); |
||||
|
0 ignored issues
–
show
$prefix of type array<integer,string> is incompatible with the type string expected by parameter $prefix of Symfony\Component\Proces...essBuilder::setPrefix().
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 231 | } |
||||
| 232 | |||||
| 233 | $builderArgs = parent::createChildProcessArgs($input); |
||||
| 234 | } else { |
||||
| 235 | $forcedRefs = array(); |
||||
| 236 | if ($input->getOption('set-reference')) { |
||||
| 237 | foreach ($input->getOption('set-reference') as $refSpec) { |
||||
| 238 | $ref = explode(':', $refSpec, 2); |
||||
| 239 | if (count($ref) < 2 || $ref[0] === '') { |
||||
| 240 | throw new \InvalidArgumentException("Invalid reference specification: '$refSpec'"); |
||||
| 241 | } |
||||
| 242 | $forcedRefs[$ref[0]] = $ref[1]; |
||||
| 243 | } |
||||
| 244 | } |
||||
| 245 | $migrationContext = array( |
||||
| 246 | 'useTransactions' => !$input->getOption('no-transactions'), |
||||
| 247 | 'defaultLanguageCode' => $input->getOption('default-language'), |
||||
| 248 | 'adminUserLogin' => $input->getOption('admin-login'), |
||||
| 249 | 'forceExecution' => $force, |
||||
| 250 | 'forcedReferences' => $forcedRefs |
||||
| 251 | ); |
||||
| 252 | } |
||||
| 253 | |||||
| 254 | // Allow forcing handling of sigchild. Useful on eg. Debian and Ubuntu |
||||
| 255 | if ($input->getOption('force-sigchild-enabled')) { |
||||
| 256 | Process::forceSigchildEnabled(true); |
||||
| 257 | } |
||||
| 258 | |||||
| 259 | $aborted = false; |
||||
| 260 | $executed = 0; |
||||
| 261 | $failed = 0; |
||||
| 262 | $skipped = 0; |
||||
| 263 | $total = count($toExecute); |
||||
| 264 | |||||
| 265 | foreach ($toExecute as $name => $migrationDefinition) { |
||||
| 266 | // let's skip migrations that we know are invalid - user was warned and he decided to proceed anyway |
||||
| 267 | if ($migrationDefinition->status == MigrationDefinition::STATUS_INVALID) { |
||||
| 268 | $this->writeln("<comment>Skipping migration (invalid definition?) Path: ".$migrationDefinition->path."</comment>", self::$VERBOSITY_CHILD); |
||||
|
0 ignored issues
–
show
self::VERBOSITY_CHILD of type double is incompatible with the type integer expected by parameter $verbosity of Kaliop\eZMigrationBundle...tractCommand::writeln().
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 269 | $skipped++; |
||||
| 270 | continue; |
||||
| 271 | } |
||||
| 272 | |||||
| 273 | $this->writeln("<info>Processing $name</info>", self::$VERBOSITY_CHILD); |
||||
| 274 | |||||
| 275 | if ($input->getOption('separate-process')) { |
||||
| 276 | |||||
| 277 | try { |
||||
| 278 | $this->executeMigrationInSeparateProcess($migrationDefinition, $migrationService, $builder, $builderArgs); |
||||
|
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
Comprehensibility
Best Practice
introduced
by
|
|||||
| 279 | |||||
| 280 | $executed++; |
||||
| 281 | } catch (\Exception $e) { |
||||
| 282 | $failed++; |
||||
| 283 | |||||
| 284 | $errorMessage = $e->getMessage(); |
||||
| 285 | if ($errorMessage != $this->subProcessErrorString) { |
||||
| 286 | /// @todo move these bits of strings to class constants |
||||
| 287 | $errorMessage = preg_replace('/^\n*(\[[0-9]*\])?(Migration failed|Failure after migration end)! Reason: +/', '', $errorMessage); |
||||
| 288 | /// @todo atm this is impossible case - executeMigrationInSeparateProcess does not know enough |
||||
| 289 | /// to throw an AfterMigrationExecutionException |
||||
| 290 | if ($e instanceof AfterMigrationExecutionException) { |
||||
| 291 | $errorMessage = "Failure after migration end! Path: " . $migrationDefinition->path . ", Reason: " . $errorMessage; |
||||
| 292 | } else { |
||||
| 293 | $errorMessage = "Migration failed! Path: " . $migrationDefinition->path . ", Reason: " . $errorMessage; |
||||
| 294 | } |
||||
| 295 | |||||
| 296 | $this->writeErrorln("\n<error>$errorMessage</error>"); |
||||
| 297 | } |
||||
| 298 | |||||
| 299 | if (!$input->getOption('ignore-failures')) { |
||||
| 300 | $aborted = true; |
||||
| 301 | break; |
||||
| 302 | } |
||||
| 303 | } |
||||
| 304 | |||||
| 305 | } else { |
||||
| 306 | |||||
| 307 | try { |
||||
| 308 | $this->executeMigrationInProcess($migrationDefinition, $migrationService, $migrationContext); |
||||
|
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||
| 309 | |||||
| 310 | $executed++; |
||||
| 311 | // in case the 1st mig changes values to the refs, we avoid injecting them in the 2nd mig and later |
||||
| 312 | $migrationContext['forcedReferences'] = array(); |
||||
| 313 | } catch (\Exception $e) { |
||||
| 314 | $failed++; |
||||
| 315 | |||||
| 316 | $errorMessage = $e->getMessage(); |
||||
| 317 | if ($e instanceof AfterMigrationExecutionException) { |
||||
| 318 | $errorMessage = "Failure after migration end! Path: " . $migrationDefinition->path . ", Reason: " . $errorMessage; |
||||
| 319 | } else { |
||||
| 320 | $errorMessage = "Migration failed! Path: " . $migrationDefinition->path . ", Reason: " . $errorMessage; |
||||
| 321 | } |
||||
| 322 | $this->writeErrorln("\n<error>$errorMessage</error>"); |
||||
| 323 | |||||
| 324 | if (!$input->getOption('ignore-failures')) { |
||||
| 325 | $aborted = true; |
||||
| 326 | break; |
||||
| 327 | } |
||||
| 328 | } |
||||
| 329 | |||||
| 330 | } |
||||
| 331 | } |
||||
| 332 | |||||
| 333 | $missed = $total - $executed - $failed - $skipped; |
||||
| 334 | |||||
| 335 | if ($aborted && $missed > 0) { |
||||
| 336 | $this->writeErrorln("\n<error>Migration execution aborted</error>"); |
||||
| 337 | } |
||||
| 338 | |||||
| 339 | $this->writeln("Migrations executed: $executed, failed: $failed, skipped: $skipped, missed: $missed", self::$VERBOSITY_CHILD); |
||||
| 340 | |||||
| 341 | // We do not return an error code > 0 if migrations fail but , but only on proper fatals. |
||||
| 342 | // The parent will analyze the output of the child process to gather the number of executed/failed migrations anyway |
||||
| 343 | return 0; |
||||
| 344 | } |
||||
| 345 | |||||
| 346 | /** |
||||
| 347 | * @param string $type |
||||
| 348 | * @param string $buffer |
||||
| 349 | * @param null|\Symfony\Component\Process\Process $process |
||||
| 350 | */ |
||||
| 351 | public function onChildProcessOutput($type, $buffer, $process=null) |
||||
| 352 | { |
||||
| 353 | $lines = explode("\n", trim($buffer)); |
||||
| 354 | |||||
| 355 | foreach ($lines as $line) { |
||||
| 356 | if (preg_match('/Migrations executed: ([0-9]+), failed: ([0-9]+), skipped: ([0-9]+)/', $line, $matches)) { |
||||
| 357 | $this->migrationsDone[Migration::STATUS_DONE] += $matches[1]; |
||||
| 358 | $this->migrationsDone[Migration::STATUS_FAILED] += $matches[2]; |
||||
| 359 | $this->migrationsDone[Migration::STATUS_SKIPPED] += $matches[3]; |
||||
| 360 | |||||
| 361 | // swallow the recap lines unless we are in verbose mode |
||||
| 362 | if ($this->verbosity <= Output::VERBOSITY_NORMAL) { |
||||
| 363 | return; |
||||
| 364 | } |
||||
| 365 | } |
||||
| 366 | |||||
| 367 | // we tag the output with the id of the child process |
||||
| 368 | if (trim($line) !== '') { |
||||
| 369 | $msg = '[' . ($process ? $process->getPid() : ''). '] ' . trim($line); |
||||
| 370 | if ($type == 'err') { |
||||
| 371 | $this->writeErrorln($msg, OutputInterface::VERBOSITY_QUIET, OutputInterface::OUTPUT_RAW); |
||||
| 372 | } else { |
||||
| 373 | // swallow output of child processes in quiet mode |
||||
| 374 | $this->writeLn($msg, self::$VERBOSITY_CHILD, OutputInterface::OUTPUT_RAW); |
||||
|
0 ignored issues
–
show
self::VERBOSITY_CHILD of type double is incompatible with the type integer expected by parameter $verbosity of Kaliop\eZMigrationBundle...tractCommand::writeln().
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 375 | } |
||||
| 376 | } |
||||
| 377 | } |
||||
| 378 | } |
||||
| 379 | |||||
| 380 | /** |
||||
| 381 | * @param string[] $paths |
||||
| 382 | * @param $migrationService |
||||
| 383 | * @param bool $force |
||||
| 384 | * @param bool $isChild when not in child mode, do not waste time parsing migrations |
||||
| 385 | * @return MigrationDefinition[] parsed or unparsed, depending on |
||||
| 386 | * |
||||
| 387 | * @todo this does not scale well with many definitions or migrations |
||||
| 388 | */ |
||||
| 389 | protected function buildMigrationsList($paths, $migrationService, $force = false, $isChild = false) |
||||
| 390 | { |
||||
| 391 | $migrationDefinitions = $migrationService->getMigrationsDefinitions($paths); |
||||
| 392 | $migrations = $migrationService->getMigrations(); |
||||
| 393 | |||||
| 394 | $this->migrationsAlreadyDone = array(Migration::STATUS_DONE => 0, Migration::STATUS_FAILED => 0, Migration::STATUS_SKIPPED => 0, Migration::STATUS_STARTED => 0); |
||||
| 395 | |||||
| 396 | $allowedStatuses = array(Migration::STATUS_TODO); |
||||
| 397 | if ($force) { |
||||
| 398 | $allowedStatuses = array_merge($allowedStatuses, array(Migration::STATUS_DONE, Migration::STATUS_FAILED, Migration::STATUS_SKIPPED)); |
||||
| 399 | } |
||||
| 400 | |||||
| 401 | // filter away all migrations except 'to do' ones |
||||
| 402 | $toExecute = array(); |
||||
| 403 | foreach ($migrationDefinitions as $name => $migrationDefinition) { |
||||
| 404 | if (!isset($migrations[$name]) || (($migration = $migrations[$name]) && in_array($migration->status, $allowedStatuses))) { |
||||
| 405 | $toExecute[$name] = $isChild ? $migrationService->parseMigrationDefinition($migrationDefinition) : $migrationDefinition; |
||||
| 406 | } |
||||
| 407 | // save the list of non-executable migrations as well (even when using 'force') |
||||
| 408 | if (!$isChild && isset($migrations[$name]) && (($migration = $migrations[$name]) && $migration->status != Migration::STATUS_TODO)) { |
||||
| 409 | $this->migrationsAlreadyDone[$migration->status]++; |
||||
| 410 | } |
||||
| 411 | } |
||||
| 412 | |||||
| 413 | // if user wants to execute 'all' migrations: look for some which are registered in the database even if not |
||||
| 414 | // found by the loader |
||||
| 415 | if (empty($paths)) { |
||||
| 416 | foreach ($migrations as $migration) { |
||||
| 417 | if (in_array($migration->status, $allowedStatuses) && !isset($toExecute[$migration->name])) { |
||||
| 418 | $migrationDefinitions = $migrationService->getMigrationsDefinitions(array($migration->path)); |
||||
| 419 | if (count($migrationDefinitions)) { |
||||
| 420 | $migrationDefinition = $migrationDefinitions->reset(); |
||||
| 421 | $toExecute[$migration->name] = $isChild ? $migrationService->parseMigrationDefinition($migrationDefinition) : $migrationDefinition; |
||||
| 422 | } else { |
||||
| 423 | // q: shall we raise a warning here ? |
||||
| 424 | } |
||||
| 425 | } |
||||
| 426 | } |
||||
| 427 | } |
||||
| 428 | |||||
| 429 | ksort($toExecute); |
||||
| 430 | |||||
| 431 | return $toExecute; |
||||
| 432 | } |
||||
| 433 | |||||
| 434 | /** |
||||
| 435 | * We use a more compact output when there are *many* migrations |
||||
| 436 | * @param MigrationDefinition[] $toExecute |
||||
| 437 | * @param array $paths |
||||
| 438 | * @param InputInterface $input |
||||
| 439 | * @param OutputInterface $output |
||||
| 440 | */ |
||||
| 441 | protected function printMigrationsList($toExecute, InputInterface $input, OutputInterface $output, $paths = array()) |
||||
| 442 | { |
||||
| 443 | $output->writeln('Found ' . count($toExecute) . ' migrations in ' . count($paths) . ' directories'); |
||||
| 444 | $output->writeln('In the same directories, migrations previously executed: ' . $this->migrationsAlreadyDone[Migration::STATUS_DONE] . |
||||
| 445 | ', failed: ' . $this->migrationsAlreadyDone[Migration::STATUS_FAILED] . ', skipped: '. $this->migrationsAlreadyDone[Migration::STATUS_SKIPPED]); |
||||
| 446 | if ($this->migrationsAlreadyDone[Migration::STATUS_STARTED]) { |
||||
| 447 | $output->writeln('<info>In the same directories, migrations currently executing: ' . $this->migrationsAlreadyDone[Migration::STATUS_STARTED] . '</info>'); |
||||
| 448 | } |
||||
| 449 | } |
||||
| 450 | |||||
| 451 | /** |
||||
| 452 | * @param MigrationDefinition[] $toExecute |
||||
| 453 | * @return array key: folder name, value: number of migrations found |
||||
| 454 | */ |
||||
| 455 | protected function groupMigrationsByPath($toExecute) |
||||
| 456 | { |
||||
| 457 | $paths = array(); |
||||
| 458 | foreach ($toExecute as $name => $migrationDefinition) { |
||||
| 459 | $path = dirname($migrationDefinition->path); |
||||
| 460 | if (!isset($paths[$path])) { |
||||
| 461 | $paths[$path] = 1; |
||||
| 462 | } else { |
||||
| 463 | $paths[$path]++; |
||||
| 464 | } |
||||
| 465 | } |
||||
| 466 | |||||
| 467 | ksort($paths); |
||||
| 468 | |||||
| 469 | return $paths; |
||||
| 470 | } |
||||
| 471 | |||||
| 472 | /** |
||||
| 473 | * Returns the command-line arguments needed to execute a separate subprocess that will run a set of migrations |
||||
| 474 | * (except path, which should be added after this call) |
||||
| 475 | * @param InputInterface $input |
||||
| 476 | * @return array |
||||
| 477 | * @todo check if it is a good idea to pass on the current verbosity |
||||
| 478 | * @todo shall we pass to child processes the `survive-disconnected-tty` flag? |
||||
| 479 | */ |
||||
| 480 | protected function createChildProcessArgs(InputInterface $input) |
||||
| 481 | { |
||||
| 482 | $kernel = $this->getContainer()->get('kernel'); |
||||
| 483 | |||||
| 484 | // mandatory args and options |
||||
| 485 | $builderArgs = array( |
||||
| 486 | $this->getConsoleFile(), // sf console |
||||
| 487 | self::COMMAND_NAME, // name of sf command. Can we get it from the Application instead of hardcoding? |
||||
| 488 | '--env=' . $kernel->getEnvironment(), // sf env |
||||
| 489 | '--child' |
||||
| 490 | ); |
||||
| 491 | // sf/ez env options |
||||
| 492 | if (!$kernel->isDebug()) { |
||||
| 493 | $builderArgs[] = '--no-debug'; |
||||
| 494 | } |
||||
| 495 | if ($input->getOption('siteaccess')) { |
||||
| 496 | $builderArgs[] = '--siteaccess=' . $input->getOption('siteaccess'); |
||||
| 497 | } |
||||
| 498 | switch ($this->verbosity) { |
||||
| 499 | // no propagation of 'quiet' mode, as we always need to have at least the child output with executed migs |
||||
| 500 | case OutputInterface::VERBOSITY_VERBOSE: |
||||
| 501 | $builderArgs[] = '-v'; |
||||
| 502 | break; |
||||
| 503 | case OutputInterface::VERBOSITY_VERY_VERBOSE: |
||||
| 504 | $builderArgs[] = '-vv'; |
||||
| 505 | break; |
||||
| 506 | case OutputInterface::VERBOSITY_DEBUG: |
||||
| 507 | $builderArgs[] = '-vvv'; |
||||
| 508 | break; |
||||
| 509 | } |
||||
| 510 | // 'optional' options |
||||
| 511 | // note: options 'clear-cache', 'no-interaction', 'path' and 'survive-disconnected-tty' we never propagate |
||||
| 512 | if ($input->getOption('admin-login')) { |
||||
| 513 | $builderArgs[] = '--admin-login=' . $input->getOption('admin-login'); |
||||
| 514 | } |
||||
| 515 | if ($input->getOption('default-language')) { |
||||
| 516 | $builderArgs[] = '--default-language=' . $input->getOption('default-language'); |
||||
| 517 | } |
||||
| 518 | if ($input->getOption('force')) { |
||||
| 519 | $builderArgs[] = '--force'; |
||||
| 520 | } |
||||
| 521 | // useful in case the subprocess has a migration step of type process/run |
||||
| 522 | if ($input->getOption('force-sigchild-enabled')) { |
||||
| 523 | $builderArgs[] = '--force-sigchild-enabled'; |
||||
| 524 | } |
||||
| 525 | if ($input->getOption('ignore-failures')) { |
||||
| 526 | $builderArgs[] = '--ignore-failures'; |
||||
| 527 | } |
||||
| 528 | if ($input->getOption('no-transactions')) { |
||||
| 529 | $builderArgs[] = '--no-transactions'; |
||||
| 530 | } |
||||
| 531 | if ($input->getOption('separate-process')) { |
||||
| 532 | $builderArgs[] = '--separate-process'; |
||||
| 533 | } |
||||
| 534 | if ($input->getOption('set-reference')) { |
||||
| 535 | foreach ($input->getOption('set-reference') as $refSpec) { |
||||
| 536 | $builderArgs[] = '--set-reference=' . $refSpec; |
||||
| 537 | } |
||||
| 538 | } |
||||
| 539 | if ($input->getOption('child-process-php-ini-config')) { |
||||
| 540 | foreach ($input->getOption('child-process-php-ini-config') as $iniSpec) { |
||||
| 541 | $builderArgs[] = '--child-process-php-ini-config=' . $iniSpec; |
||||
| 542 | } |
||||
| 543 | } |
||||
| 544 | return $builderArgs; |
||||
| 545 | } |
||||
| 546 | } |
||||
| 547 |