Skip to content

fix: Make the PHP executable into an array #314

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 5 additions & 11 deletions src/Input/ChildCommandFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -27,8 +26,11 @@
*/
final readonly class ChildCommandFactory
{
/**
* @param list<string> $phpExecutable
*/
public function __construct(
private string $phpExecutable,
private array $phpExecutable,
private string $scriptPath,
private string $commandName,
private InputDefinition $commandDefinition,
Expand All @@ -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)),
Expand All @@ -74,14 +76,6 @@ private function getForwardedOptions(InputInterface $input): array
);
}

/**
* @return list<string>
*/
private function getEscapedPhpExecutable(): array
{
return explode(' ', $this->phpExecutable);
}

/**
* @return list<string|bool|int|float|null|array<string|bool|int|float|null>>
*/
Expand Down
15 changes: 12 additions & 3 deletions src/ParallelExecutorFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -46,6 +48,7 @@ final class ParallelExecutorFactory
* @param Closure(InputInterface, OutputInterface):void $runAfterLastCommand
* @param Closure(InputInterface, OutputInterface, list<string>):void $runBeforeBatch
* @param Closure(InputInterface, OutputInterface, list<string>):void $runAfterBatch
* @param list<string> $phpExecutable
* @param array<string, string> $extraEnvironmentVariables
* @param Closure(): void $processTick
*/
Expand All @@ -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,
Expand Down Expand Up @@ -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<string> $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;
}
Expand Down
13 changes: 10 additions & 3 deletions src/Process/PhpExecutableFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,23 @@ public static function tryToFind(): ?string
return false === $phpExecutable ? null : $phpExecutable;
}

public static function find(): string
/**
* @return list<string>
*/
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
Expand Down
7 changes: 5 additions & 2 deletions tests/Fixtures/Command/LegacyCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 9 additions & 9 deletions tests/Input/ChildCommandFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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';

Expand Down Expand Up @@ -110,7 +110,7 @@ public static function childProvider(): iterable
$commandDefinition,
$input,
[
$phpExecutable,
$phpExecutable[0],
$scriptPath,
$commandName,
'group2',
Expand Down Expand Up @@ -166,7 +166,7 @@ public static function childProvider(): iterable
$commandDefinition,
$input,
[
$phpExecutable,
$phpExecutable[0],
$scriptPath,
$commandName,
'group2',
Expand Down Expand Up @@ -214,7 +214,7 @@ public static function childProvider(): iterable
$commandDefinition,
$input,
[
$phpExecutable,
$phpExecutable[0],
$scriptPath,
$commandName,
'--child',
Expand All @@ -232,7 +232,7 @@ public static function childProvider(): iterable
);

return [
'',
[],
$scriptPath,
$commandName,
$commandDefinition,
Expand All @@ -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,
Expand All @@ -279,7 +279,7 @@ public static function childProvider(): iterable
);

return [
'',
[],
$scriptPath,
'',
$commandDefinition,
Expand All @@ -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(),
Expand Down
40 changes: 39 additions & 1 deletion tests/ParallelExecutorFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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';
Expand Down
6 changes: 3 additions & 3 deletions tests/ParallelExecutorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,7 @@ public function test_it_can_launch_configured_child_processes(): void
$noop,
'ø',
new ChildCommandFactory(
$phpExecutable,
[$phpExecutable],
$scriptPath,
$commandName,
$commandDefinition,
Expand Down Expand Up @@ -1188,7 +1188,7 @@ private static function createChildProcessExecutor(
$runAfterBatch,
$progressSymbol,
new ChildCommandFactory(
__FILE__,
[__FILE__],
__FILE__,
'',
new InputDefinition(),
Expand Down Expand Up @@ -1237,7 +1237,7 @@ private static function createMainProcessExecutor(
$runAfterBatch,
$progressSymbol,
new ChildCommandFactory(
__FILE__,
[__FILE__],
__FILE__,
'import:something',
new InputDefinition([
Expand Down
Loading