diff --git a/src/Input/ChildCommandFactory.php b/src/Input/ChildCommandFactory.php index 7433abb..6056c2e 100644 --- a/src/Input/ChildCommandFactory.php +++ b/src/Input/ChildCommandFactory.php @@ -18,7 +18,6 @@ use Webmozart\Assert\Assert; use function array_filter; use function array_map; -use function explode; use function Safe\getcwd; use function sprintf; @@ -27,8 +26,11 @@ */ final readonly class ChildCommandFactory { + /** + * @param list $phpExecutable + */ public function __construct( - private string $phpExecutable, + private array $phpExecutable, private string $scriptPath, private string $commandName, private InputDefinition $commandDefinition, @@ -51,7 +53,7 @@ private function createBaseCommand( InputInterface $input ): array { return array_filter([ - ...$this->getEscapedPhpExecutable(), + ...$this->phpExecutable, $this->scriptPath, $this->commandName, ...array_map(strval(...), self::getArguments($input)), @@ -74,14 +76,6 @@ private function getForwardedOptions(InputInterface $input): array ); } - /** - * @return list - */ - private function getEscapedPhpExecutable(): array - { - return explode(' ', $this->phpExecutable); - } - /** * @return list> */ diff --git a/src/ParallelExecutorFactory.php b/src/ParallelExecutorFactory.php index e1d0ef5..8a62dcb 100644 --- a/src/ParallelExecutorFactory.php +++ b/src/ParallelExecutorFactory.php @@ -24,6 +24,8 @@ use Webmozarts\Console\Parallelization\Process\StandardSymfonyProcessFactory; use Webmozarts\Console\Parallelization\Process\SymfonyProcessLauncherFactory; use function chr; +use function explode; +use function is_string; use function Safe\getcwd; use function str_starts_with; use const DIRECTORY_SEPARATOR; @@ -46,6 +48,7 @@ final class ParallelExecutorFactory * @param Closure(InputInterface, OutputInterface):void $runAfterLastCommand * @param Closure(InputInterface, OutputInterface, list):void $runBeforeBatch * @param Closure(InputInterface, OutputInterface, list):void $runAfterBatch + * @param list $phpExecutable * @param array $extraEnvironmentVariables * @param Closure(): void $processTick */ @@ -64,7 +67,7 @@ private function __construct( private Closure $runBeforeBatch, private Closure $runAfterBatch, private string $progressSymbol, - private string $phpExecutable, + private array $phpExecutable, private string $scriptPath, private string $workingDirectory, private ?array $extraEnvironmentVariables, @@ -228,11 +231,17 @@ public function withProgressSymbol(string $progressSymbol): self /** * The path of the PHP executable. It is the executable that will be used * to spawn the child process(es). + * + * @param string|list $phpExecutable */ - public function withPhpExecutable(string $phpExecutable): self + public function withPhpExecutable(string|array $phpExecutable): self { + $normalizedExecutable = is_string($phpExecutable) + ? explode(' ', $phpExecutable) + : $phpExecutable; + $clone = clone $this; - $clone->phpExecutable = $phpExecutable; + $clone->phpExecutable = $normalizedExecutable; return $clone; } diff --git a/src/Process/PhpExecutableFinder.php b/src/Process/PhpExecutableFinder.php index 548de01..01100e4 100644 --- a/src/Process/PhpExecutableFinder.php +++ b/src/Process/PhpExecutableFinder.php @@ -35,16 +35,23 @@ public static function tryToFind(): ?string return false === $phpExecutable ? null : $phpExecutable; } - public static function find(): string + /** + * @return list + */ + public static function find(): array { - $phpExecutable = self::getFinder()->find(); + $finder = self::getFinder(); + $phpExecutable = $finder->find(false); Assert::notFalse( $phpExecutable, 'Could not find the PHP executable.', ); - return $phpExecutable; + return array_merge( + [$phpExecutable], + $finder->findArguments(), + ); } private static function getFinder(): SymfonyPhpExecutableFinder diff --git a/tests/Fixtures/Command/LegacyCommand.php b/tests/Fixtures/Command/LegacyCommand.php index 570c558..790bdd1 100644 --- a/tests/Fixtures/Command/LegacyCommand.php +++ b/tests/Fixtures/Command/LegacyCommand.php @@ -160,11 +160,14 @@ private static function getProgressSymbol(): string return chr(200); } - private static function detectPhpExecutable(): string + private static function detectPhpExecutable(): array { self::$calls['static'][] = [__FUNCTION__, func_get_args()]; - return PhpExecutableFinder::find().' -d memory_limit=-1'; + return [ + ...PhpExecutableFinder::find(), + '-d memory_limit=-1', + ]; } private static function getWorkingDirectory(ContainerInterface $container): string diff --git a/tests/Input/ChildCommandFactoryTest.php b/tests/Input/ChildCommandFactoryTest.php index c4fcc8b..0420b92 100644 --- a/tests/Input/ChildCommandFactoryTest.php +++ b/tests/Input/ChildCommandFactoryTest.php @@ -32,7 +32,7 @@ final class ChildCommandFactoryTest extends TestCase { #[DataProvider('childProvider')] public function test_it_can_launch_configured_child_processes( - string $phpExecutable, + array $phpExecutable, string $scriptPath, string $commandName, InputDefinition $commandDefinition, @@ -53,7 +53,7 @@ public function test_it_can_launch_configured_child_processes( public static function childProvider(): iterable { - $phpExecutable = __FILE__; + $phpExecutable = [__FILE__]; $scriptPath = __DIR__.'/../../bin/console'; $commandName = 'import:something'; @@ -110,7 +110,7 @@ public static function childProvider(): iterable $commandDefinition, $input, [ - $phpExecutable, + $phpExecutable[0], $scriptPath, $commandName, 'group2', @@ -166,7 +166,7 @@ public static function childProvider(): iterable $commandDefinition, $input, [ - $phpExecutable, + $phpExecutable[0], $scriptPath, $commandName, 'group2', @@ -214,7 +214,7 @@ public static function childProvider(): iterable $commandDefinition, $input, [ - $phpExecutable, + $phpExecutable[0], $scriptPath, $commandName, '--child', @@ -232,7 +232,7 @@ public static function childProvider(): iterable ); return [ - '', + [], $scriptPath, $commandName, $commandDefinition, @@ -255,7 +255,7 @@ public static function childProvider(): iterable ); return [ - '/path/to/php -dmemory_limit=1', + ['/path/to/php', '-dmemory_limit=1'], $scriptPath, $commandName, $commandDefinition, @@ -279,7 +279,7 @@ public static function childProvider(): iterable ); return [ - '', + [], $scriptPath, '', $commandDefinition, @@ -300,7 +300,7 @@ public function test_it_cannot_create_a_factory_with_an_invalid_script_path(): v $this->expectExceptionMessage('The script file could not be found at the path "path/to/unknown" (working directory: '.$cwd.')'); new ChildCommandFactory( - __FILE__, + [__FILE__], 'path/to/unknown', 'import:something', new InputDefinition(), diff --git a/tests/ParallelExecutorFactoryTest.php b/tests/ParallelExecutorFactoryTest.php index d83392a..f921726 100644 --- a/tests/ParallelExecutorFactoryTest.php +++ b/tests/ParallelExecutorFactoryTest.php @@ -97,7 +97,7 @@ public function test_it_can_create_a_configured_executor(): void $callable6, $progressSymbol, new ChildCommandFactory( - self::FILE_1, + [self::FILE_1], self::FILE_2, $commandName, $definition, @@ -111,6 +111,44 @@ public function test_it_can_create_a_configured_executor(): void self::assertEquals($expected, $executor); } + public function test_it_escapes_the_php_executable_when_necessary(): void + { + $commandName = 'import:items'; + $definition = new InputDefinition(); + $errorHandler = new FakeErrorHandler(); + + $callable0 = self::createCallable(0); + $callable1 = self::createCallable(1); + $callable2 = self::createCallable(2); + + $executorWithStringPhpExecutable = ParallelExecutorFactory::create( + $callable0, + $callable1, + $callable2, + $commandName, + $definition, + $errorHandler, + ) + ->withPhpExecutable('/path/to/php -dmemory_limit=128M') + ->build(); + + $executorWithArrayPhpExecutable = ParallelExecutorFactory::create( + $callable0, + $callable1, + $callable2, + $commandName, + $definition, + $errorHandler, + ) + ->withPhpExecutable([ + '/path/to/php', + '-dmemory_limit=128M', + ]) + ->build(); + + self::assertEquals($executorWithArrayPhpExecutable, $executorWithStringPhpExecutable); + } + public function test_it_sets_the_batch_size_to_the_segment_size_by_default(): void { $commandName = 'import:items'; diff --git a/tests/ParallelExecutorTest.php b/tests/ParallelExecutorTest.php index 6b297bc..b191e19 100644 --- a/tests/ParallelExecutorTest.php +++ b/tests/ParallelExecutorTest.php @@ -561,7 +561,7 @@ public function test_it_can_launch_configured_child_processes(): void $noop, 'ΓΈ', new ChildCommandFactory( - $phpExecutable, + [$phpExecutable], $scriptPath, $commandName, $commandDefinition, @@ -1188,7 +1188,7 @@ private static function createChildProcessExecutor( $runAfterBatch, $progressSymbol, new ChildCommandFactory( - __FILE__, + [__FILE__], __FILE__, '', new InputDefinition(), @@ -1237,7 +1237,7 @@ private static function createMainProcessExecutor( $runAfterBatch, $progressSymbol, new ChildCommandFactory( - __FILE__, + [__FILE__], __FILE__, 'import:something', new InputDefinition([