MigrateCommand::createChildProcessArgs()   F
last analyzed

Complexity

Conditions 13
Paths 1024

Size

Total Lines 53
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 22.2222

Importance

Changes 0
Metric Value
cc 13
eloc 34
nc 1024
nop 1
dl 0
loc 53
ccs 18
cts 29
cp 0.6207
crap 22.2222
rs 2.45
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Consol...on\ConfirmationQuestion was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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);
0 ignored issues
show
Bug introduced by
$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 ignore-type  annotation

136
                $builder->setPrefix(/** @scrutinizer ignore-type */ $prefix);
Loading history...
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);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $builderArgs does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $builder does not seem to be defined for all execution paths leading up to this point.
Loading history...
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);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $migrationContext does not seem to be defined for all execution paths leading up to this point.
Loading history...
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);
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

326
                        $this->writeLn($buffer, /** @scrutinizer ignore-type */ self::$VERBOSITY_CHILD, OutputInterface::OUTPUT_RAW);
Loading history...
327
                    }
328
                }
329
                :
330
                function($type, $buffer) {
0 ignored issues
show
Unused Code introduced by
The parameter $buffer 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 ignore-unused  annotation

330
                function($type, /** @scrutinizer ignore-unused */ $buffer) {

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...
Unused Code introduced by
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 ignore-unused  annotation

330
                function(/** @scrutinizer ignore-unused */ $type, $buffer) {

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...
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) {
0 ignored issues
show
introduced by
$migration is of type Kaliop\eZMigrationBundle\API\Value\Migration, thus it always evaluated to true.
Loading history...
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(
0 ignored issues
show
Bug introduced by
The method ask() does not exist on Symfony\Component\Console\Helper\HelperInterface. It seems like you code against a sub-type of Symfony\Component\Console\Helper\HelperInterface such as Symfony\Component\Console\Helper\DialogHelper. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

466
            if (!$dialog->/** @scrutinizer ignore-call */ ask(
Loading history...
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