Skip to content

Commit ac39e71

Browse files
authored
fix: Fix incorrect PHP executable escaping (#329)
1 parent c31f07e commit ac39e71

File tree

3 files changed

+79
-27
lines changed

3 files changed

+79
-27
lines changed

src/ParallelExecutorFactory.php

+2-7
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
use Webmozarts\Console\Parallelization\Process\StandardSymfonyProcessFactory;
2525
use Webmozarts\Console\Parallelization\Process\SymfonyProcessLauncherFactory;
2626
use function chr;
27-
use function explode;
2827
use function is_string;
2928
use function Safe\getcwd;
3029
use function str_starts_with;
@@ -232,16 +231,12 @@ public function withProgressSymbol(string $progressSymbol): self
232231
* The path of the PHP executable. It is the executable that will be used
233232
* to spawn the child process(es).
234233
*
235-
* @param string|list<string> $phpExecutable
234+
* @param string|list<string> $phpExecutable e.g. ['/path/to/php', '-dmemory_limit=512M']
236235
*/
237236
public function withPhpExecutable(string|array $phpExecutable): self
238237
{
239-
$normalizedExecutable = is_string($phpExecutable)
240-
? explode(' ', $phpExecutable)
241-
: $phpExecutable;
242-
243238
$clone = clone $this;
244-
$clone->phpExecutable = $normalizedExecutable;
239+
$clone->phpExecutable = is_string($phpExecutable) ? [$phpExecutable] : $phpExecutable;
245240

246241
return $clone;
247242
}

tests/Input/ChildCommandFactoryTest.php

+25
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,31 @@ public static function childProvider(): iterable
270270
];
271271
})();
272272

273+
yield 'enriched PHP executable with space (array case)' => (static function () use (
274+
$scriptPath,
275+
$commandName
276+
) {
277+
[$input, $commandDefinition] = self::createInput(
278+
[],
279+
[],
280+
);
281+
282+
return [
283+
['/path/to/my php', '-dmemory_limit=1'],
284+
$scriptPath,
285+
$commandName,
286+
$commandDefinition,
287+
$input,
288+
[
289+
'/path/to/my php',
290+
'-dmemory_limit=1',
291+
$scriptPath,
292+
$commandName,
293+
'--child',
294+
],
295+
];
296+
})();
297+
273298
yield 'no PHP executable or command' => (static function () use (
274299
$scriptPath
275300
) {

tests/ParallelExecutorFactoryTest.php

+52-20
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use PHPUnit\Framework\Attributes\CoversClass;
1717
use PHPUnit\Framework\Attributes\DataProvider;
1818
use PHPUnit\Framework\TestCase;
19+
use ReflectionClass;
1920
use Symfony\Component\Console\Input\InputDefinition;
2021
use Symfony\Component\Filesystem\Filesystem;
2122
use Symfony\Component\Process\Process;
@@ -111,8 +112,11 @@ public function test_it_can_create_a_configured_executor(): void
111112
self::assertEquals($expected, $executor);
112113
}
113114

114-
public function test_it_escapes_the_php_executable_when_necessary(): void
115-
{
115+
#[DataProvider('phpExecutableProvider')]
116+
public function test_it_escapes_the_php_executable_when_necessary(
117+
string|array $phpExecutable,
118+
array $expected,
119+
): void {
116120
$commandName = 'import:items';
117121
$definition = new InputDefinition();
118122
$errorHandler = new FakeErrorHandler();
@@ -121,32 +125,60 @@ public function test_it_escapes_the_php_executable_when_necessary(): void
121125
$callable1 = self::createCallable(1);
122126
$callable2 = self::createCallable(2);
123127

124-
$executorWithStringPhpExecutable = ParallelExecutorFactory::create(
128+
$factory = ParallelExecutorFactory::create(
125129
$callable0,
126130
$callable1,
127131
$callable2,
128132
$commandName,
129133
$definition,
130134
$errorHandler,
131135
)
132-
->withPhpExecutable('/path/to/php -dmemory_limit=128M')
133-
->build();
136+
->withPhpExecutable($phpExecutable);
134137

135-
$executorWithArrayPhpExecutable = ParallelExecutorFactory::create(
136-
$callable0,
137-
$callable1,
138-
$callable2,
139-
$commandName,
140-
$definition,
141-
$errorHandler,
142-
)
143-
->withPhpExecutable([
144-
'/path/to/php',
145-
'-dmemory_limit=128M',
146-
])
147-
->build();
138+
$phpExecutableReflection = (new ReflectionClass($factory::class))->getProperty('phpExecutable');
139+
$phpExecutableReflection->setAccessible(true);
140+
141+
$actual = $phpExecutableReflection->getValue($factory);
142+
143+
self::assertSame($expected, $actual);
144+
}
148145

149-
self::assertEquals($executorWithArrayPhpExecutable, $executorWithStringPhpExecutable);
146+
public static function phpExecutableProvider(): iterable
147+
{
148+
yield 'simple path' => [
149+
'/path/to/php',
150+
['/path/to/php'],
151+
];
152+
153+
yield 'path with space' => [
154+
'/path/to/my php',
155+
['/path/to/my php'],
156+
];
157+
158+
yield 'path with space (array)' => [
159+
['/path/to/my php'],
160+
['/path/to/my php'],
161+
];
162+
163+
yield 'path with php setting (incorrect)' => [
164+
'/path/to/php -dmemory_limit=128M',
165+
['/path/to/php -dmemory_limit=128M'],
166+
];
167+
168+
yield 'path with php setting (array)' => [
169+
['/path/to/php', '-dmemory_limit=128M'],
170+
['/path/to/php', '-dmemory_limit=128M'],
171+
];
172+
173+
yield 'path with space and with php setting (incorrect)' => [
174+
'/path/to/my php -dmemory_limit=128M',
175+
['/path/to/my php -dmemory_limit=128M'],
176+
];
177+
178+
yield 'path with space and with php setting (array)' => [
179+
['/path/to/my php', '-dmemory_limit=128M'],
180+
['/path/to/my php', '-dmemory_limit=128M'],
181+
];
150182
}
151183

152184
public function test_it_sets_the_batch_size_to_the_segment_size_by_default(): void
@@ -234,7 +266,7 @@ public function test_it_can_create_an_executor_with_default_values(
234266
string $expectedSymbol,
235267
string $expectedPhpExecutable,
236268
string $expectedScriptPath,
237-
string $expectedWorkingDirectory
269+
string $expectedWorkingDirectory,
238270
): void {
239271
$cleanUpWorkingDirectory = self::moveToWorkingDirectory($workingDirectory);
240272
$cleanUpEnvironmentVariables = EnvironmentVariables::setVariables($environmentVariables);

0 commit comments

Comments
 (0)