Passed
Push — master ( d6ce84...9c42e9 )
by Gaetano
10:07
created

MigrateCommand::buildMigrationsList()   C

Complexity

Conditions 12
Paths 12

Size

Total Lines 42
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 20.4698

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 23
nc 12
nop 3
dl 0
loc 42
ccs 11
cts 18
cp 0.6111
crap 20.4698
rs 6.9666
c 1
b 0
f 0

How to fix   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;
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\Core\MigrationService;
16
use Kaliop\eZMigrationBundle\Core\Process\Process;
17
use Kaliop\eZMigrationBundle\Core\Process\ProcessBuilder;
18
19
/**
20
 * Command to execute the available migration definitions.
21
 */
22
class MigrateCommand extends AbstractCommand
23
{
24
    // in between QUIET and NORMAL
25
    const VERBOSITY_CHILD = 0.5;
26
27
    protected $subProcessTimeout = 86400;
28
    protected $subProcessErrorString = '';
29
30
    const COMMAND_NAME = 'kaliop:migration:migrate';
31
32
    /**
33
     * Set up the command.
34
     *
35
     * Define the name, options and help text.
36
     */
37 94
    protected function configure()
38
    {
39 94
        parent::configure();
40
41
        $this
42 94
            ->setName(self::COMMAND_NAME)
43 94
            ->setAliases(array('kaliop:migration:update'))
44 94
            ->setDescription('Execute available migration definitions.')
45
            // nb: when adding options, remember to forward them to sub-commands executed in 'separate-process' mode
46 94
            ->addOption('admin-login', 'a', InputOption::VALUE_REQUIRED, "Login of admin account used whenever elevated privileges are needed (user id 14 used by default)")
47 94
            ->addOption('clear-cache', 'c', InputOption::VALUE_NONE, "Clear the cache after the command finishes")
48 94
            ->addOption('default-language', 'l', InputOption::VALUE_REQUIRED, "Default language code that will be used if no language is provided in migration steps")
49 94
            ->addOption('force', 'f', InputOption::VALUE_NONE, "Force (re)execution of migrations already DONE, SKIPPED or FAILED. Use with great care!")
50 94
            ->addOption('ignore-failures', 'i', InputOption::VALUE_NONE, "Keep executing migrations even if one fails")
51 94
            ->addOption('no-interaction', 'n', InputOption::VALUE_NONE, "Do not ask any interactive question")
52 94
            ->addOption('no-transactions', 'u', InputOption::VALUE_NONE, "Do not use a repository transaction to wrap each migration. Unsafe, but needed for legacy slot handlers")
53 94
            ->addOption('path', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, "The directory or file to load the migration definitions from")
54 94
            ->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")
55 94
            ->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")
56 94
            ->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")
57 94
            ->addOption('child', null, InputOption::VALUE_NONE, "*DO NOT USE* Internal option for when forking separate processes")
58 94
            ->setHelp(<<<EOT
59 94
The <info>kaliop:migration:migrate</info> command loads and executes migrations:
60
61
    <info>./ezpublish/console kaliop:migration:migrate</info>
62
63
You can optionally specify the path to migration definitions with <info>--path</info>:
64
65
    <info>./ezpublish/console kaliop:migrations:migrate --path=/path/to/bundle/version_directory --path=/path/to/bundle/version_directory/single_migration_file</info>
66
EOT
67
            );
68 94
    }
69
70
    /**
71
     * Execute the command.
72
     *
73
     * @param InputInterface $input
74
     * @param OutputInterface $output
75
     * @return null|int null or 0 if everything went fine, or an error code
76
     */
77 58
    protected function execute(InputInterface $input, OutputInterface $output)
78
    {
79 58
        $start = microtime(true);
80
81 58
        $this->setOutput($output);
82 58
        $this->setVerbosity($output->getVerbosity());
83
84 58
        if ($input->getOption('child') && $output->getVerbosity() <= OutputInterface::VERBOSITY_NORMAL) {
85
            $this->setVerbosity(self::VERBOSITY_CHILD);
86
        }
87
88 58
        $this->getContainer()->get('ez_migration_bundle.step_executed_listener.tracing')->setOutput($output);
89
90 58
        $migrationService = $this->getMigrationService();
91
92 58
        $force = $input->getOption('force');
93
94 58
        $toExecute = $this->buildMigrationsList($input->getOption('path'), $migrationService, $force);
0 ignored issues
show
Bug introduced by
It seems like $input->getOption('path') can also be of type boolean and string; however, parameter $paths of Kaliop\eZMigrationBundle...::buildMigrationsList() does only seem to accept string[], maybe add an additional type check? ( Ignorable by Annotation )

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

94
        $toExecute = $this->buildMigrationsList(/** @scrutinizer ignore-type */ $input->getOption('path'), $migrationService, $force);
Loading history...
95
96 58
        if (!count($toExecute)) {
97
            $output->writeln('<info>No migrations to execute</info>');
98
            return 0;
99
        }
100
101 58
        $this->printMigrationsList($toExecute, $input, $output);
102
103
        if (!$input->getOption('child')) {
104 58
            // ask user for confirmation to make changes
105
            if (!$this->askForConfirmation($input, $output)) {
106
                return 0;
107
            }
108 58
        }
109 2
110 2
        if ($input->getOption('separate-process')) {
111 2
            $builder = new ProcessBuilder();
112 2
            $executableFinder = new PhpExecutableFinder();
113
            if (false !== $php = $executableFinder->find()) {
114 2
                $builder->setPrefix($php);
115
            }
116
            $builderArgs = $this->createChildProcessArgs($input);
117
        }
118 58
119 1
        // For cli scripts, this means: do not die if anyone yanks out our stdout.
120
        if ($input->getOption('survive-disconnected-tty')) {
121
            ignore_user_abort(true);
122
        }
123 58
124 1
        // allow forcing handling of sigchild. Useful on eg. Debian and Ubuntu
125
        if ($input->getOption('force-sigchild-enabled')) {
126
            Process::forceSigchildEnabled(true);
127 58
        }
128 58
129 58
        $aborted = false;
130 58
        $executed = 0;
131 58
        $failed = 0;
132
        $skipped = 0;
133
        $total = count($toExecute);
134 58
135
        /** @var MigrationDefinition $migrationDefinition */
136
        foreach ($toExecute as $name => $migrationDefinition) {
137 58
138 9
            // let's skip migrations that we know are invalid - user was warned and he decided to proceed anyway
139 9
            if ($migrationDefinition->status == MigrationDefinition::STATUS_INVALID) {
140 9
                $output->writeln("<comment>Skipping $name</comment>");
141
                $skipped++;
142
                continue;
143 49
            }
144
145 49
            $this->writeln("<info>Processing $name</info>");
146
147
            if ($input->getOption('separate-process')) {
148
149 2
                try {
150
                    $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...
151 2
152
                    $executed++;
153
                } catch (\Exception $e) {
154
                    $failed++;
155
156
157
                    $errorMessage = $e->getMessage();
158
                    // we probably have already echoed the error message while the subprocess was executing, avoid repeating it
159
                    if ($errorMessage != $this->subProcessErrorString) {
160
                        if ($e instanceof AfterMigrationExecutionException) {
161
                            $errorMessage = "Failure after migration end! Reason: " . $errorMessage;
162
                        } else {
163
                            $errorMessage = "Migration failed! Reason: " . $errorMessage;
164
                        }
165
166
                        $this->writeErrorln("\n<error>$errorMessage</error>");
167
                    }
168
169
                    if (!$input->getOption('ignore-failures')) {
170 2
                        $aborted = true;
171
                        break;
172
                    }
173
                }
174
175
            } else {
176
177 47
                try {
178
                    $this->executeMigrationInProcess($migrationDefinition, $force, $migrationService, $input);
179 39
180 8
                    $executed++;
181 8
                } catch (\Exception $e) {
182
                    $failed++;
183 8
184
                    $this->writeErrorln("\n<error>Migration failed! Reason: " . $e->getMessage() . "</error>");
185 8
186 8
                    if (!$input->getOption('ignore-failures')) {
187 49
                        $aborted = true;
188
                        break;
189
                    }
190
                }
191
192
            }
193
        }
194 58
195 8
        $missed = $total - $executed - $failed - $skipped;
196
197 50
        if ($aborted) {
198
            if ($missed > 0) {
199
                $this->writeErrorln("\n<error>Migration execution aborted</error>");
200
            }
201
        } else {
202
            // NB: as per the Sf doc at https://symfony.com/doc/2.7/console/calling_commands.html, the 'cache:clear'
203
            // command should be run 'at the end', as they change some class definitions
204 58
            if ($input->getOption('clear-cache')) {
205 58
                $command = $this->getApplication()->find('cache:clear');
206
                $inputArray = new ArrayInput(array('command' => 'cache:clear'));
207 58
                $command->run($inputArray, $output);
208 58
            }
209
        }
210 2
211
        $this->writeln("\nExecuted $executed migrations, failed $failed, skipped $skipped" . ($missed ? ", missed $missed" : ''));
212 56
213
        $time = microtime(true) - $start;
214
        if ($input->getOption('separate-process')) {
215 58
            // in case of using subprocesses, we can not measure max memory used
216
            $this->writeln("<info>Time taken: ".sprintf('%.2f', $time)." secs</info>");
217
        } else {
218
            $this->writeln("<info>Time taken: ".sprintf('%.2f', $time)." secs, memory: ".sprintf('%.2f', (memory_get_peak_usage(true) / 1000000)). ' MB</info>');
219
        }
220
221
        return $failed;
222
    }
223
224 47
    /**
225
     * @param MigrationDefinition $migrationDefinition
226 47
     * @param bool $force
227 47
     * @param MigrationService $migrationService
228 47
     * @param InputInterface $input
229 47
     */
230 47
    protected function executeMigrationInProcess($migrationDefinition, $force, $migrationService, $input)
231 47
    {
232
        $migrationService->executeMigration(
233 39
            $migrationDefinition,
234
            !$input->getOption('no-transactions'),
235
            $input->getOption('default-language'),
0 ignored issues
show
Bug introduced by
It seems like $input->getOption('default-language') can also be of type string[]; however, parameter $defaultLanguageCode of Kaliop\eZMigrationBundle...ice::executeMigration() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

235
            /** @scrutinizer ignore-type */ $input->getOption('default-language'),
Loading history...
236
            $input->getOption('admin-login'),
237
            $force
238
        );
239
    }
240
241
    /**
242 2
     * @param MigrationDefinition $migrationDefinition
243
     * @param MigrationService $migrationService
244
     * @param ProcessBuilder $builder
245 2
     * @param array $builderArgs
246 2
     * @param bool $feedback
247
     */
248 2
    protected function executeMigrationInSeparateProcess($migrationDefinition, $migrationService, $builder, $builderArgs, $feedback = true)
249 2
    {
250
        $process = $builder
251
            ->setArguments(array_merge($builderArgs, array('--path=' . $migrationDefinition->path)))
252 2
            ->getProcess();
253
254
        if ($feedback) {
255 2
            $this->writeln('<info>Executing: ' . $process->getCommandLine() . '</info>', OutputInterface::VERBOSITY_VERBOSE);
256
        }
257
258
        $this->subProcessErrorString = '';
259
260
        // allow long migrations processes by default
261
        $process->setTimeout($this->subProcessTimeout);
262 2
263 2
        // and give immediate feedback to the user...
264 2
        // NB: if the subprocess writes to stderr then terminates with non-0 exit code, this will lead us to echoing the
265 2
        // error text twice, once here and once at the end of execution of this command.
266
        // In order to avoid that, since we can not know at this time what the subprocess exit code will be, we
267
        // do print the error text now, and compare it to what we gt at the end...
268 2
        $process->run(
269 2
            $feedback ?
270
                function($type, $buffer) {
271 2
                    if ($type == 'err') {
272 2
                        $this->subProcessErrorString .= $buffer;
273
                        $this->writeErrorln($buffer, OutputInterface::VERBOSITY_QUIET, OutputInterface::OUTPUT_RAW);
274
                    } else {
275 2
                        // swallow output of child processes in quiet mode
276
                        $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

276
                        $this->writeLn($buffer, /** @scrutinizer ignore-type */ self::VERBOSITY_CHILD, OutputInterface::OUTPUT_RAW);
Loading history...
277
                    }
278
                }
279
                :
280
                function($type, $buffer) {
0 ignored issues
show
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

280
                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...
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

280
                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...
281
                }
282
        );
283
284
        if (!$process->isSuccessful()) {
285
            $errorOutput = $process->getErrorOutput();
286
            /// @todo should we always add the exit code to the error message, even when $errorOutput is not null ?
287
            if ($errorOutput === '') {
288
                $errorOutput = "(separate process used to execute migration failed with no stderr output. Its exit code was: " . $process->getExitCode();
289
                if ($process->getExitCode() == -1) {
290
                    $errorOutput .= ". If you are using Debian or Ubuntu linux, please consider using the --force-sigchild-enabled option.";
291 2
                }
292
                $errorOutput .= ")";
293 2
            }
294
            throw new \Exception($errorOutput);
295
        }
296 2
297
        // There are cases where the separate process dies halfway but does not return a non-zero code.
298
        // That's why we double-check here if the migration is still tagged as 'started'...
299
        /** @var Migration $migration */
300
        $migration = $migrationService->getMigration($migrationDefinition->name);
301
302
        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...
303
            // q: shall we add the migration to the db as failed? In doubt, we let it become a ghost, disappeared without a trace...
304
            throw new \Exception("After the separate process charged to execute the migration finished, the migration can not be found in the database any more.");
305
        } else if ($migration->status == Migration::STATUS_STARTED) {
306
            $errorMsg = "The separate process charged to execute the migration left it in 'started' state. Most likely it died halfway through execution.";
307
            $migrationService->endMigration(New Migration(
308 2
                $migration->name,
309
                $migration->md5,
310
                $migration->path,
311
                $migration->executionDate,
312
                Migration::STATUS_FAILED,
313
                ($migration->executionError != '' ? ($errorMsg . ' ' . $migration->executionError) : $errorMsg)
314
            ));
315
            throw new \Exception($errorMsg);
316
        }
317
    }
318 58
319
    /**
320 58
     * @param string[] $paths
321 58
     * @param MigrationService $migrationService
322
     * @param bool $force when true, look not only for TODO migrations, but also DONE, SKIPPED, FAILED ones (we still omit STARTED and SUSPENDED ones)
323 58
     * @return MigrationDefinition[]
324 58
     *
325 2
     * @todo optimize. This does not scale well with many definitions or migrations
326
     */
327
    protected function buildMigrationsList($paths, $migrationService, $force = false)
328
    {
329 58
        $migrationDefinitions = $migrationService->getMigrationsDefinitions($paths);
330 58
        $migrations = $migrationService->getMigrations();
331 58
332 58
        $allowedStatuses = array(Migration::STATUS_TODO);
333
        if ($force) {
334
            $allowedStatuses = array_merge($allowedStatuses, array(Migration::STATUS_DONE, Migration::STATUS_FAILED, Migration::STATUS_SKIPPED));
335
        }
336
337
        // filter away all migrations except 'to do' ones
338 58
        $toExecute = array();
339
        foreach ($migrationDefinitions as $name => $migrationDefinition) {
340
            if (!isset($migrations[$name]) || (($migration = $migrations[$name]) && in_array($migration->status, $allowedStatuses))) {
341
                $toExecute[$name] = $migrationService->parseMigrationDefinition($migrationDefinition);
342
            }
343
        }
344
345
        // if user wants to execute 'all' migrations: look for those which are registered in the database even if not
346
        // found by the loader
347
        if (empty($paths)) {
348
            foreach ($migrations as $migration) {
349
                if (in_array($migration->status, $allowedStatuses) && !isset($toExecute[$migration->name])) {
350
                    try {
351
                        $migrationDefinitions = $migrationService->getMigrationsDefinitions(array($migration->path));
352 58
                        if (count($migrationDefinitions)) {
353
                            // q: shall we raise a warning here if migrations found > 1?
354 58
                            $migrationDefinition = $migrationDefinitions->reset();
355
                            $toExecute[$migration->name] = $migrationService->parseMigrationDefinition($migrationDefinition);
356
                        } else {
357
                            throw new \Exception("Migration definition not found at path '$migration->path'");
358
                        }
359
                    } catch (\Exception $e) {
360
                        $this->writeErrorln("Error while loading definition for migration '{$migration->name}' registered in the database, skipping it: " . $e->getMessage());
361
                    }
362
                }
363
            }
364 58
        }
365
366 58
        ksort($toExecute);
367 58
368 58
        return $toExecute;
369 58
    }
370 58
371 9
    /**
372
     * @param MigrationDefinition[] $toExecute
373 58
     * @param InputInterface $input
374 58
     * @param OutputInterface $output
375 58
     *
376 58
     * @todo use a more compact output when there are *many* migrations
377
     */
378
    protected function printMigrationsList($toExecute, InputInterface $input, OutputInterface $output)
379
    {
380 58
        $data = array();
381 58
        $i = 1;
382
        foreach ($toExecute as $name => $migrationDefinition) {
383 58
            $notes = '';
384 58
            if ($migrationDefinition->status != MigrationDefinition::STATUS_PARSED) {
385 58
                $notes = '<error>' . $migrationDefinition->parsingError . '</error>';
386
            }
387
            $data[] = array(
388 58
                $i++,
389 58
                $name,
390
                $notes
391 58
            );
392
        }
393 58
394
        if (!$input->getOption('child')) {
395
            $table = new Table($output);
396
            $table
397
                ->setHeaders(array('#', 'Migration', 'Notes'))
398
                ->setRows($data);
399
            $table->render();
400
        }
401
402
        $this->writeln('');
403
    }
404
405 58
    protected function askForConfirmation(InputInterface $input, OutputInterface $output, $nonIteractiveOutput = "=============================================\n")
406 58
    {
407
        if ($input->isInteractive() && !$input->getOption('no-interaction')) {
408
            $dialog = $this->getHelperSet()->get('question');
409
            if (!$dialog->ask(
0 ignored issues
show
Bug introduced by
The method ask() does not exist on Symfony\Component\Console\Helper\Helper. It seems like you code against a sub-type of Symfony\Component\Console\Helper\Helper such as Symfony\Component\Console\Helper\QuestionHelper or 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

409
            if (!$dialog->/** @scrutinizer ignore-call */ ask(
Loading history...
410 58
                $input,
411
                $output,
412
                new ConfirmationQuestion('<question>Careful, the database will be modified. Do you want to continue Y/N ?</question>', false)
413
            )
414
            ) {
415
                $output->writeln('<error>Migration execution cancelled!</error>');
416
                return 0;
417
            }
418
        } else {
419
            if ($nonIteractiveOutput != '') {
420 2
                $this->writeln("$nonIteractiveOutput");
421
            }
422 2
        }
423
424
        return 1;
425
    }
426 2
427 2
    /**
428 2
     * Returns the command-line arguments needed to execute a migration in a separate subprocess
429 2
     * (except path, which should be added after this call)
430
     * @param InputInterface $input
431
     * @return array
432 2
     * @todo check if it is a good idea to pass on the current verbosity
433 2
     */
434
    protected function createChildProcessArgs(InputInterface $input)
435 2
    {
436
        $kernel = $this->getContainer()->get('kernel');
437
438
        // mandatory args and options
439
        $builderArgs = array(
440 2
            $this->getConsoleFile(), // sf console
441
            self::COMMAND_NAME, // name of sf command. Can we get it from the Application instead of hardcoding?
442
            '--env=' . $kernel->getEnvironment(), // sf env
443 2
            '--child'
444
        );
445
        // sf/ez env options
446 2
        if (!$kernel->isDebug()) {
447
            $builderArgs[] = '--no-debug';
448
        }
449 2
        if ($input->getOption('siteaccess')) {
450
            $builderArgs[] = '--siteaccess='.$input->getOption('siteaccess');
451
        }
452
        switch ($this->verbosity) {
453 2
            case OutputInterface::VERBOSITY_VERBOSE:
454
                $builderArgs[] = '-v';
455
                break;
456
            case OutputInterface::VERBOSITY_VERY_VERBOSE:
457 2
                $builderArgs[] = '-vv';
458
                break;
459
            case OutputInterface::VERBOSITY_DEBUG:
460
                $builderArgs[] = '-vvv';
461
                break;
462
        }
463
        // 'optional' options
464
        // note: options 'clear-cache', 'ignore-failures', 'no-interaction', 'path', 'separate-process' and 'survive-disconnected-tty' we never propagate
465 2
        if ($input->getOption('admin-login')) {
466
            $builderArgs[] = '--admin-login=' . $input->getOption('admin-login');
467 2
        }
468 2
        if ($input->getOption('default-language')) {
469 2
            $builderArgs[] = '--default-language=' . $input->getOption('default-language');
470 2
        }
471
        if ($input->getOption('force')) {
472
            $builderArgs[] = '--force';
473
        }
474
        if ($input->getOption('no-transactions')) {
475
            $builderArgs[] = '--no-transactions';
476
        }
477
        // useful in case the subprocess has a migration step of type process/run
478
        if ($input->getOption('force-sigchild-enabled')) {
479
            $builderArgs[] = '--force-sigchild-enabled';
480
        }
481
482
        return $builderArgs;
483
    }
484
485
    /**
486
     * Returns the file-path of the symfony console in use, based on simple heuristics
487
     * @return string
488
     * @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
489
     */
490
    protected function getConsoleFile()
491
    {
492
        if (strpos($_SERVER['argv'][0], 'phpunit') !== false) {
493
            $kernelDir = $this->getContainer()->get('kernel')->getRootDir();
494
            if (is_file("$kernelDir/console")) {
495
                return "$kernelDir/console";
496
            }
497
            if (is_file("$kernelDir/../bin/console")) {
498
                return "$kernelDir/../bin/console";
499
            }
500
            throw new \Exception("Can not determine the name of the symfony console file in use for running as separate process");
501
        }
502
503
        return $_SERVER['argv'][0]; // sf console
504
    }
505
}
506