diff --git a/composer.json b/composer.json index d0fe6f5..888ee20 100644 --- a/composer.json +++ b/composer.json @@ -9,8 +9,7 @@ "nette/utils": "^4.1", "react/child-process": "^0.6.5", "react/event-loop": "^1.6", - "react/socket": "^1.17", - "symfony/console": "^6.0|^7.0|^8.0" + "react/socket": "^1.17" }, "require-dev": { "phpunit/phpunit": "^13.2", diff --git a/src/CommandLine/WorkerCommandLineFactory.php b/src/CommandLine/WorkerCommandLineFactory.php index b363fad..0946544 100644 --- a/src/CommandLine/WorkerCommandLineFactory.php +++ b/src/CommandLine/WorkerCommandLineFactory.php @@ -4,11 +4,6 @@ namespace Symplify\EasyParallel\CommandLine; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symplify\EasyParallel\Exception\ParallelShouldNotHappenException; -use Symplify\EasyParallel\Reflection\CommandFromReflectionFactory; - /** * @api * @see \Symplify\EasyParallel\Tests\CommandLine\WorkerCommandLineFactoryTest @@ -18,63 +13,51 @@ private const string OPTION_DASHES = '--'; /** - * These options are not relevant for nested worker command line. + * These options are not relevant for nested worker command line, either + * because they are handled explicitly below or are framework globals that + * must not leak into the worker sub-process. * * @var string[] */ - private const array EXCLUDED_OPTION_NAMES = ['output-format']; - - private CommandFromReflectionFactory $commandFromReflectionFactory; - - public function __construct() - { - $this->commandFromReflectionFactory = new CommandFromReflectionFactory(); - } + private const array EXCLUDED_OPTION_NAMES = [ + 'output-format', + 'ansi', + 'no-ansi', + 'help', + 'quiet', + 'verbose', + 'version', + 'no-interaction', + ]; /** - * @param class-string $mainCommandClass + * The worker command name is always the first token, so the sub-process resolves the + * worker command directly instead of falling back to the application's default command. + * + * @param array $optionValues option name => resolved value + * @param string[] $paths */ public function create( string $baseScript, - string $mainCommandClass, string $workerCommandName, - string $pathsOptionName, ?string $projectConfigFile, - InputInterface $input, + array $optionValues, + array $paths, string $identifier, int $port ): string { - $commandArguments = array_slice($_SERVER['argv'], 1); - $args = array_merge([PHP_BINARY, $baseScript], $commandArguments); - - $mainCommand = $this->commandFromReflectionFactory->create($mainCommandClass); - - if ($mainCommand->getName() === null) { - $errorMessage = sprintf('The command name for "%s" is missing', $mainCommand::class); - throw new ParallelShouldNotHappenException($errorMessage); - } - - $mainCommandName = $mainCommand->getName(); - - $processCommandArray = []; - - foreach ($args as $arg) { - // skip command name - if ($arg === $mainCommandName) { - break; - } - - $processCommandArray[] = escapeshellarg((string) $arg); - } + $processCommandArray = [ + escapeshellarg(PHP_BINARY), + escapeshellarg($baseScript), + $workerCommandName, + ]; - $processCommandArray[] = $workerCommandName; if ($projectConfigFile !== null) { $processCommandArray[] = '--config'; $processCommandArray[] = escapeshellarg($projectConfigFile); } - $mainCommandOptionNames = $this->getCommandOptionNames($mainCommand); - $processCommandOptions = $this->mirrorCommandOptions($input, $mainCommandOptionNames); + $processCommandOptions = $this->mirrorCommandOptions($optionValues); $processCommandArray = array_merge($processCommandArray, $processCommandOptions); // for TCP local server @@ -84,8 +67,6 @@ public function create( $processCommandArray[] = '--identifier'; $processCommandArray[] = escapeshellarg($identifier); - /** @var string[] $paths */ - $paths = (array) $input->getArgument($pathsOptionName); foreach ($paths as $path) { $processCommandArray[] = escapeshellarg($path); } @@ -101,39 +82,21 @@ public function create( return implode(' ', $processCommandArray); } - /** - * @return string[] - */ - private function getCommandOptionNames(Command $command): array - { - $inputDefinition = $command->getDefinition(); - - $optionNames = []; - foreach ($inputDefinition->getOptions() as $inputOption) { - $optionNames[] = $inputOption->getName(); - } - - return $optionNames; - } - /** * Keeps all options that are allowed in check command options * - * @param string[] $mainCommandOptionNames + * @param array $optionValues * @return string[] */ - private function mirrorCommandOptions(InputInterface $input, array $mainCommandOptionNames): array + private function mirrorCommandOptions(array $optionValues): array { $processCommandOptions = []; - foreach ($mainCommandOptionNames as $mainCommandOptionName) { - if ($this->shouldSkipOption($input, $mainCommandOptionName)) { + foreach ($optionValues as $optionName => $optionValue) { + if (in_array($optionName, self::EXCLUDED_OPTION_NAMES, true)) { continue; } - /** @var bool|string|null $optionValue */ - $optionValue = $input->getOption($mainCommandOptionName); - // skip clutter if ($optionValue === null) { continue; @@ -141,30 +104,21 @@ private function mirrorCommandOptions(InputInterface $input, array $mainCommandO if (is_bool($optionValue)) { if ($optionValue) { - $processCommandOptions[] = self::OPTION_DASHES . $mainCommandOptionName; + $processCommandOptions[] = self::OPTION_DASHES . $optionName; } continue; } - if ($mainCommandOptionName === 'memory-limit') { + if ($optionName === 'memory-limit') { // symfony/console does not accept -1 as value without assign - $processCommandOptions[] = '--' . $mainCommandOptionName . '=' . $optionValue; + $processCommandOptions[] = '--' . $optionName . '=' . $optionValue; } else { - $processCommandOptions[] = self::OPTION_DASHES . $mainCommandOptionName; - $processCommandOptions[] = escapeshellarg($optionValue); + $processCommandOptions[] = self::OPTION_DASHES . $optionName; + $processCommandOptions[] = escapeshellarg((string) $optionValue); } } return $processCommandOptions; } - - private function shouldSkipOption(InputInterface $input, string $optionName): bool - { - if (! $input->hasOption($optionName)) { - return true; - } - - return in_array($optionName, self::EXCLUDED_OPTION_NAMES, true); - } } diff --git a/src/Reflection/CommandFromReflectionFactory.php b/src/Reflection/CommandFromReflectionFactory.php deleted file mode 100644 index 1dcf154..0000000 --- a/src/Reflection/CommandFromReflectionFactory.php +++ /dev/null @@ -1,40 +0,0 @@ - $className - */ - public function create(string $className): Command - { - $commandReflectionClass = new ReflectionClass($className); - - $command = $commandReflectionClass->newInstanceWithoutConstructor(); - $parentClassReflection = $commandReflectionClass->getParentClass(); - - if (! $parentClassReflection instanceof ReflectionClass) { - throw new ParallelShouldNotHappenException(); - } - - $parentConstructorReflectionMethod = $parentClassReflection->getConstructor(); - if (! $parentConstructorReflectionMethod instanceof ReflectionMethod) { - throw new ParallelShouldNotHappenException(); - } - - $parentConstructorReflectionMethod->invoke($command); - - return $command; - } -} diff --git a/tests/CommandLine/Source/MainCommand.php b/tests/CommandLine/Source/MainCommand.php deleted file mode 100644 index 07e8f34..0000000 --- a/tests/CommandLine/Source/MainCommand.php +++ /dev/null @@ -1,26 +0,0 @@ -setName('main'); - - $this->addArgument(TestOption::PATHS, InputArgument::IS_ARRAY); - $this->addOption(TestOption::OUTPUT_FORMAT, null, InputOption::VALUE_REQUIRED | InputOption::VALUE_OPTIONAL); - } -} diff --git a/tests/CommandLine/Source/SomeService.php b/tests/CommandLine/Source/SomeService.php deleted file mode 100644 index 119b5f9..0000000 --- a/tests/CommandLine/Source/SomeService.php +++ /dev/null @@ -1,9 +0,0 @@ -workerCommandLineFactory = new WorkerCommandLineFactory(); - $this->commandFromReflectionFactory = new CommandFromReflectionFactory(); } /** - * @param class-string $commandClass - * @param array $inputParameters + * @param array $optionValues + * @param string[] $paths */ #[DataProvider('provideData')] - public function test( - string $commandClass, - string $pathsOptionName, - array $inputParameters, - string $expectedCommand - ): void { - $inputDefinition = $this->prepareProcessCommandDefinition($commandClass); - $arrayInput = new ArrayInput($inputParameters, $inputDefinition); - + public function test(array $optionValues, array $paths, string $expectedCommand): void + { $workerCommandLine = $this->workerCommandLineFactory->create( self::DUMMY_MAIN_SCRIPT, - $commandClass, 'worker', - $pathsOptionName, null, - $arrayInput, + $optionValues, + $paths, 'identifier', 2000 ); @@ -64,65 +43,18 @@ public function test( public static function provideData(): Iterator { - $cliInputOptions = array_slice($_SERVER['argv'], 1); - - $expectedCommandLinesString = self::createExpectedCommandLinesString($cliInputOptions); + $expectedCommandLinesString = self::createExpectedCommandLinesString(); - yield [ - MainCommand::class, - TestOption::PATHS, - [ - self::COMMAND => 'process', - TestOption::PATHS => ['src'], - ], - $expectedCommandLinesString, - ]; + yield [[], ['src'], $expectedCommandLinesString]; - yield [ - MainCommand::class, - TestOption::PATHS, - [ - self::COMMAND => 'process', - TestOption::PATHS => ['src'], - '--' . TestOption::OUTPUT_FORMAT => 'console', - ], - $expectedCommandLinesString, - ]; + // output-format is excluded, so it must not change the result + yield [[TestOption::OUTPUT_FORMAT => 'console'], ['src'], $expectedCommandLinesString]; } - /** - * @param class-string $mainCommandClass - */ - private function prepareProcessCommandDefinition(string $mainCommandClass): InputDefinition - { - $mainCommand = $this->commandFromReflectionFactory->create($mainCommandClass); - - $inputDefinition = $mainCommand->getDefinition(); - - // not sure why, but the 1st argument "command" is missing; this is needed for a command name - $arguments = $inputDefinition->getArguments(); - $commandInputArgument = new InputArgument(self::COMMAND, InputArgument::REQUIRED); - $arguments = array_merge([$commandInputArgument], $arguments); - - $inputDefinition->setArguments($arguments); - - return $inputDefinition; - } - - /** - * @param string[] $cliInputOptions - */ - private static function createExpectedCommandLinesString(array $cliInputOptions): string + private static function createExpectedCommandLinesString(): string { $commandLineString = "'" . PHP_BINARY . "' '" . self::DUMMY_MAIN_SCRIPT . "'"; - // in some cases, the test does not have any options/args, e.g. when running it like "vendor/bin/phpunit" - // vs." vendor/bin/phpunit " - if ($cliInputOptions !== []) { - $cliInputOptionsAsString = implode("' '", $cliInputOptions); - $commandLineString .= " '" . $cliInputOptionsAsString . "'"; - } - return $commandLineString . " worker --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi"; } }