Passed
Branch main (a5ccba)
by Gaetano
04:40
created

executeMigrationInSeparateProcess()   C

Complexity

Conditions 12
Paths 16

Size

Total Lines 75
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 31.418

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 40
c 1
b 0
f 0
nc 16
nop 5
dl 0
loc 75
ccs 19
cts 39
cp 0.4872
crap 31.418
rs 6.9666

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 Symfony\Component\Console\Input\ArrayInput;
6
use Symfony\Component\Console\Input\InputInterface;
7
use Symfony\Component\Console\Input\InputOption;
8
use Symfony\Component\Console\Output\OutputInterface;
9
use Symfony\Component\Console\Helper\Table;
10
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...
11
use Symfony\Component\Process\PhpExecutableFinder;
12
use Kaliop\eZMigrationBundle\API\Value\MigrationDefinition;
13
use Kaliop\eZMigrationBundle\API\Value\Migration;
14
use Kaliop\eZMigrationBundle\API\Exception\AfterMigrationExecutionException;
15
use Kaliop\eZMigrationBundle\API\Exception\MigrationBundleException;
16
use Kaliop\eZMigrationBundle\Core\MigrationService;
17
use Kaliop\eZMigrationBundle\Core\Process\Process;
18
use Kaliop\eZMigrationBundle\Core\Process\ProcessBuilder;
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
        }
140
141
        // For cli scripts, this means: do not die if anyone yanks out our stdout.
142 97
        if ($input->getOption('survive-disconnected-tty')) {
143 1
            ignore_user_abort(true);
144
        }
145
146
        // allow forcing handling of sigchild. Useful on eg. Debian and Ubuntu
147 97
        if ($input->getOption('force-sigchild-enabled')) {
148 1
            Process::forceSigchildEnabled(true);
149
        }
150
151 97
        if ($input->getOption('set-reference') && !$input->getOption('separate-process')) {
152 2
            $refResolver = $this->getContainer()->get('ez_migration_bundle.reference_resolver.customreference');
153 2
            foreach ($input->getOption('set-reference') as $refSpec) {
154 2
                $ref = explode(':', $refSpec, 2);
155 2
                if (count($ref) < 2 || $ref[0] === '') {
156
                    throw new \InvalidArgumentException("Invalid reference specification: '$refSpec'");
157
                }
158 2
                $refResolver->addReference($ref[0], $ref[1], true);
159
            }
160
        }
161
162 97
        $aborted = false;
163 97
        $executed = 0;
164 97
        $failed = 0;
165 97
        $skipped = 0;
166 97
        $total = count($toExecute);
167
168
        /** @var MigrationDefinition $migrationDefinition */
169 97
        foreach ($toExecute as $name => $migrationDefinition) {
170
171
            // let's skip migrations that we know are invalid - user was warned and he decided to proceed anyway
172
            /// @todo should we save their 'skipped' status in the db?
173 97
            if ($migrationDefinition->status == MigrationDefinition::STATUS_INVALID) {
174 7
                $output->writeln("<comment>Skipping $name</comment>");
175 7
                $skipped++;
176 7
                continue;
177
            }
178
179 90
            if ($this->verbosity >= OutputInterface::VERBOSITY_VERBOSE) {
180 9
                $this->writeln("<info>Processing $name (from definition $migrationDefinition->path)</info>");
181
            } else {
182 81
                $this->writeln("<info>Processing $name</info>");
183
            }
184
185 90
            if ($input->getOption('separate-process')) {
186
187
                try {
188 5
                    $this->executeMigrationInSeparateProcess($migrationDefinition, $migrationService, $builder, $builderArgs);
0 ignored issues
show
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...
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...
189
190 5
                    $executed++;
191
                } catch (\Exception $e) {
192
                    $failed++;
193
194
                    $errorMessage = $e->getMessage();
195
                    // we probably have already echoed the error message while the subprocess was executing, avoid repeating it
196
                    if ($errorMessage != $this->subProcessErrorString) {
197
                        if ($e instanceof AfterMigrationExecutionException) {
198
                            $errorMessage = "Failure after migration end! Reason: " . $errorMessage;
199
                        } else {
200
                            $errorMessage = "Migration failed! Reason: " . $errorMessage;
201
                        }
202
203
                        $this->writeErrorln("\n<error>$errorMessage</error>");
204
                    }
205
206
                    if (!$input->getOption('ignore-failures')) {
207
                        $aborted = true;
208 5
                        break;
209
                    }
210
                }
211
212
            } else {
213
214
                try {
215 85
                    $this->executeMigrationInProcess($migrationDefinition, $force, $migrationService, $input);
216
217 57
                    $executed++;
218 28
                } catch (\Exception $e) {
219 28
                    $failed++;
220
221 28
                    $this->writeErrorln("\n<error>Migration failed! Reason: " . $e->getMessage() . "</error>");
222
223 28
                    if (!$input->getOption('ignore-failures')) {
224 28
                        $aborted = true;
225 28
                        break;
226
                    }
227
                }
228
229
            }
230
        }
231
232 97
        $missed = $total - $executed - $failed - $skipped;
233
234 97
        if ($aborted) {
235 28
            if ($missed > 0) {
236 28
                $this->writeErrorln("\n<error>Migration execution aborted</error>");
237
            }
238
        } else {
239
            // NB: as per the Sf doc at https://symfony.com/doc/2.7/console/calling_commands.html, the 'cache:clear'
240
            // command should be run 'at the end', as they change some class definitions
241 69
            if ($input->getOption('clear-cache')) {
242
                $command = $this->getApplication()->find('cache:clear');
243
                $inputArray = new ArrayInput(array('command' => 'cache:clear'));
244
                $command->run($inputArray, $output);
245
            }
246
        }
247
248 97
        $this->writeln("\nExecuted $executed migrations, failed $failed, skipped $skipped" . ($missed ? ", missed $missed" : ''));
249
250 97
        $time = microtime(true) - $start;
251 97
        if ($input->getOption('separate-process')) {
252
            // in case of using subprocesses, we can not measure max memory used
253 5
            $this->writeln("<info>Time taken: ".sprintf('%.3f', $time)." secs</info>");
254
        } else {
255 92
            $this->writeln("<info>Time taken: ".sprintf('%.3f', $time)." secs, memory: ".sprintf('%.2f', (memory_get_peak_usage(true) / 1000000)). ' MB</info>');
256
        }
257
258 97
        return $failed;
259
    }
260
261
    /**
262
     * @param MigrationDefinition $migrationDefinition
263
     * @param bool $force
264
     * @param MigrationService $migrationService
265
     * @param InputInterface $input
266
     */
267 85
    protected function executeMigrationInProcess($migrationDefinition, $force, $migrationService, $input)
268
    {
269 85
        $migrationService->executeMigration(
270 85
            $migrationDefinition,
271 85
            !$input->getOption('no-transactions'),
272 85
            $input->getOption('default-language'),
273 85
            $input->getOption('admin-login'),
274
            $force
275
        );
276 57
    }
277
278
    /**
279
     * @param MigrationDefinition $migrationDefinition
280
     * @param MigrationService $migrationService
281
     * @param ProcessBuilder $builder
282
     * @param array $builderArgs
283
     * @param bool $feedback
284
     */
285 5
    protected function executeMigrationInSeparateProcess($migrationDefinition, $migrationService, $builder, $builderArgs, $feedback = true)
286
    {
287
        $process = $builder
288 5
            ->setArguments(array_merge($builderArgs, array('--path=' . $migrationDefinition->path)))
289 5
            ->getProcess();
290
291 5
        if ($feedback) {
292 5
            $this->writeln('<info>Executing: ' . $process->getCommandLine() . '</info>', OutputInterface::VERBOSITY_NORMAL);
293
        }
294
295 5
        $this->subProcessErrorString = '';
296
297
        // allow long migrations processes by default
298 5
        $process->setTimeout($this->subProcessTimeout);
299
300
        // and give immediate feedback to the user...
301
        // NB: if the subprocess writes to stderr then terminates with non-0 exit code, this will lead us to echoing the
302
        // error text twice, once here and once at the end of execution of this command.
303
        // In order to avoid that, since we can not know at this time what the subprocess exit code will be, we
304
        // do print the error text now, and compare it to what we get at the end...
305 5
        $process->run(
306 5
            $feedback ?
307
                function($type, $buffer) {
308 4
                    if ($type == 'err') {
309 1
                        $this->subProcessErrorString .= $buffer;
310 1
                        $this->writeErrorln($buffer, OutputInterface::VERBOSITY_QUIET, OutputInterface::OUTPUT_RAW);
311
                    } else {
312
                        // swallow output of child processes in quiet mode
313 4
                        $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

313
                        $this->writeLn($buffer, /** @scrutinizer ignore-type */ self::$VERBOSITY_CHILD, OutputInterface::OUTPUT_RAW);
Loading history...
314
                    }
315 5
                }
316
                :
317
                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

317
                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

317
                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...
318 5
                }
319
        );
320
321 5
        if (!$process->isSuccessful()) {
322
            $errorOutput = $process->getErrorOutput();
323
            /// @todo should we always add the exit code to the error message, even when $errorOutput is not null ?
324
            if ($errorOutput === '') {
325
                $errorOutput = "(separate process used to execute migration failed with no stderr output. Its exit code was: " . $process->getExitCode();
326
                // We go out of our way to help the user finding the cause of the error.
327
                /// @todo another cause we might check for in case of empty $errorOutput is when error_reporting does
328
                ///       not include fatal errors (E_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR?)
329
                $errorLog = ini_get("error_log");
330
                if ($errorLog != '' && $errorLog != 'syslog') {
331
                    $errorOutput .= ". Error details might be in file $errorLog";
332
                }
333
                if ($process->getExitCode() == -1) {
334
                    $errorOutput .= ". If you are using Debian or Ubuntu linux, please consider using the --force-sigchild-enabled option.";
335
                }
336
                $errorOutput .= ")";
337
            }
338
            throw new MigrationBundleException($errorOutput);
339
        }
340
341
        // There are cases where the separate process dies halfway but does not return a non-zero code.
342
        // That's why we double-check here if the migration is still tagged as 'started'...
343
        /** @var Migration $migration */
344 5
        $migration = $migrationService->getMigration($migrationDefinition->name);
345
346 5
        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...
347
            // q: shall we add the migration to the db as failed? In doubt, we let it become a ghost, disappeared without a trace...
348
            throw new MigrationBundleException("After the separate process charged to execute the migration finished, the migration can not be found in the database any more.");
349 5
        } else if ($migration->status == Migration::STATUS_STARTED) {
350
            $errorMsg = "The separate process charged to execute the migration left it in 'started' state. Most likely it died halfway through execution.";
351
            $migrationService->endMigration(New Migration(
352
                $migration->name,
353
                $migration->md5,
354
                $migration->path,
355
                $migration->executionDate,
356
                Migration::STATUS_FAILED,
357
                ($migration->executionError != '' ? ($errorMsg . ' ' . $migration->executionError) : $errorMsg)
358
            ));
359
            throw new MigrationBundleException($errorMsg);
360
        }
361 5
    }
362
363
    /**
364
     * @param string[] $paths
365
     * @param MigrationService $migrationService
366
     * @param bool $force when true, look not only for TODO migrations, but also DONE, SKIPPED, FAILED ones (we still omit STARTED and SUSPENDED ones)
367
     * @return MigrationDefinition[]
368
     *
369
     * @todo optimize. This does not scale well with many definitions or migrations
370
     */
371 98
    protected function buildMigrationsList($paths, $migrationService, $force = false)
372
    {
373 98
        $migrationDefinitions = $migrationService->getMigrationsDefinitions($paths);
374 98
        $migrations = $migrationService->getMigrations();
375
376 98
        $allowedStatuses = array(Migration::STATUS_TODO);
377 98
        if ($force) {
378 2
            $allowedStatuses = array_merge($allowedStatuses, array(Migration::STATUS_DONE, Migration::STATUS_FAILED, Migration::STATUS_SKIPPED));
379
        }
380
381
        // filter away all migrations except 'to do' ones
382 98
        $toExecute = array();
383 98
        foreach ($migrationDefinitions as $name => $migrationDefinition) {
384 98
            if (!isset($migrations[$name]) || (($migration = $migrations[$name]) && in_array($migration->status, $allowedStatuses))) {
385 97
                $toExecute[$name] = $migrationService->parseMigrationDefinition($migrationDefinition);
386
            }
387
        }
388
389
        // if user wants to execute 'all' migrations: look for those which are registered in the database even if not
390
        // found by the loader
391 98
        if (empty($paths)) {
392
            foreach ($migrations as $migration) {
393
                if (in_array($migration->status, $allowedStatuses) && !isset($toExecute[$migration->name])) {
394
                    try {
395
                        $migrationDefinitions = $migrationService->getMigrationsDefinitions(array($migration->path));
396
                        if (count($migrationDefinitions)) {
397
                            // q: shall we raise a warning here if migrations found > 1?
398
                            $migrationDefinition = $migrationDefinitions->reset();
399
                            $toExecute[$migration->name] = $migrationService->parseMigrationDefinition($migrationDefinition);
400
                        } else {
401
                            throw new MigrationBundleException("Migration definition not found at path '$migration->path'");
402
                        }
403
                    } catch (\Exception $e) {
404
                        $this->writeErrorln("Error while loading definition for migration '{$migration->name}' registered in the database, skipping it: " . $e->getMessage());
405
                    }
406
                }
407
            }
408
        }
409
410 98
        ksort($toExecute);
411
412 98
        return $toExecute;
413
    }
414
415
    /**
416
     * @param MigrationDefinition[] $toExecute
417
     * @param InputInterface $input
418
     * @param OutputInterface $output
419
     *
420
     * @todo use a more compact output when there are *many* migrations
421
     */
422 97
    protected function printMigrationsList($toExecute, InputInterface $input, OutputInterface $output)
423
    {
424 97
        $data = array();
425 97
        $i = 1;
426 97
        foreach ($toExecute as $name => $migrationDefinition) {
427 97
            $notes = '';
428 97
            if ($migrationDefinition->status != MigrationDefinition::STATUS_PARSED) {
429 7
                $notes = '<error>' . $migrationDefinition->parsingError . '</error>';
430
            }
431 97
            $data[] = array(
432 97
                $i++,
433 97
                $name,
434 97
                $notes
435
            );
436
        }
437
438 97
        if (!$input->getOption('child')) {
439 97
            $table = new Table($output);
440
            $table
441 97
                ->setHeaders(array('#', 'Migration', 'Notes'))
442 97
                ->setRows($data);
443 97
            $table->render();
444
        }
445
446 97
        $this->writeln('');
447 97
    }
448
449 97
    protected function askForConfirmation(InputInterface $input, OutputInterface $output, $nonIteractiveOutput = "=============================================\n")
450
    {
451 97
        if ($input->isInteractive() && !$input->getOption('no-interaction')) {
452
            $dialog = $this->getHelperSet()->get('question');
453
            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

453
            if (!$dialog->/** @scrutinizer ignore-call */ ask(
Loading history...
454
                $input,
455
                $output,
456
                new ConfirmationQuestion('<question>Careful, the database will be modified. Do you want to continue Y/N ?</question>', false)
457
            )
458
            ) {
459
                $output->writeln('<error>Migration execution cancelled!</error>');
460
                return 0;
461
            }
462
        } else {
463 97
            if ($nonIteractiveOutput != '') {
464 97
                $this->writeln("$nonIteractiveOutput");
465
            }
466
        }
467
468 97
        return 1;
469
    }
470
471
    /**
472
     * Returns the command-line arguments needed to execute a migration in a separate subprocess
473
     * (except path, which should be added after this call)
474
     * @param InputInterface $input
475
     * @return array
476
     * @todo check if it is a good idea to pass on the current verbosity
477
     */
478 5
    protected function createChildProcessArgs(InputInterface $input)
479
    {
480 5
        $kernel = $this->getContainer()->get('kernel');
481
482
        // mandatory args and options
483
        $builderArgs = array(
484 5
            $this->getConsoleFile(), // sf console
485 5
            self::COMMAND_NAME, // name of sf command. Can we get it from the Application instead of hardcoding?
486 5
            '--env=' . $kernel->getEnvironment(), // sf env
487 5
            '--child'
488
        );
489
        // sf/ez env options
490 5
        if (!$kernel->isDebug()) {
491 5
            $builderArgs[] = '--no-debug';
492
        }
493 5
        if ($input->getOption('siteaccess')) {
494
            $builderArgs[] = '--siteaccess='.$input->getOption('siteaccess');
495
        }
496 5
        switch ($this->verbosity) {
497
            case OutputInterface::VERBOSITY_VERBOSE:
498 1
                $builderArgs[] = '-v';
499 1
                break;
500
            case OutputInterface::VERBOSITY_VERY_VERBOSE:
501 1
                $builderArgs[] = '-vv';
502 1
                break;
503
            case OutputInterface::VERBOSITY_DEBUG:
504
                $builderArgs[] = '-vvv';
505
                break;
506
        }
507
        // 'optional' options
508
        // note: options 'clear-cache', 'ignore-failures', 'no-interaction', 'path', 'separate-process' and 'survive-disconnected-tty' we never propagate
509 5
        if ($input->getOption('admin-login')) {
510
            $builderArgs[] = '--admin-login=' . $input->getOption('admin-login');
511
        }
512 5
        if ($input->getOption('default-language')) {
513
            $builderArgs[] = '--default-language=' . $input->getOption('default-language');
514
        }
515 5
        if ($input->getOption('force')) {
516
            $builderArgs[] = '--force';
517
        }
518
        // useful in case the subprocess has a migration step of type process/run
519 5
        if ($input->getOption('force-sigchild-enabled')) {
520
            $builderArgs[] = '--force-sigchild-enabled';
521
        }
522 5
        if ($input->getOption('no-transactions')) {
523
            $builderArgs[] = '--no-transactions';
524
        }
525 5
        if ($input->getOption('set-reference')) {
526
            foreach ($input->getOption('set-reference') as $refSpec) {
527
                $builderArgs[] = '--set-reference=' . $refSpec;
528
            }
529
        }
530 5
        return $builderArgs;
531
    }
532
533
    /**
534
     * Returns the file-path of the symfony console in use, based on simple heuristics
535
     * @return string
536
     * @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
537
     */
538 5
    protected function getConsoleFile()
539
    {
540 5
        if (strpos($_SERVER['argv'][0], 'phpunit') !== false) {
541 5
            $kernelDir = $this->getContainer()->get('kernel')->getRootDir();
542 5
            if (is_file("$kernelDir/console")) {
543
                return "$kernelDir/console";
544
            }
545 5
            if (is_file("$kernelDir/../bin/console")) {
546 5
                return "$kernelDir/../bin/console";
547
            }
548
            throw new MigrationBundleException("Can not determine the name of the symfony console file in use for running as separate process");
549
        }
550
551
        return $_SERVER['argv'][0]; // sf console
552
    }
553
}
554