diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 56242bf318de7..ece6d1c4cd0b8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -129,6 +129,7 @@ use Symfony\Component\Notifier\Recipient\Recipient; use Symfony\Component\Notifier\TexterInterface; use Symfony\Component\Notifier\Transport\TransportFactoryInterface as NotifierTransportFactoryInterface; +use Symfony\Component\Process\Messenger\RunProcessMessageHandler; use Symfony\Component\PropertyAccess\PropertyAccessor; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor; @@ -240,6 +241,12 @@ public function load(array $configs, ContainerBuilder $container) $container->registerAliasForArgument('parameter_bag', PsrContainerInterface::class); + $loader->load('process.php'); + + if (!class_exists(RunProcessMessageHandler::class)) { + $container->removeDefinition('process.messenger.process_message_handler'); + } + if ($this->hasConsole()) { $loader->load('console.php'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/process.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/process.php new file mode 100644 index 0000000000000..909b848313cc7 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/process.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Process\Messenger\RunProcessMessageHandler; + +return static function (ContainerConfigurator $container) { + $container + ->services() + ->set('process.messenger.process_message_handler', RunProcessMessageHandler::class) + ->tag('messenger.message_handler') + ; +}; diff --git a/src/Symfony/Component/Process/CHANGELOG.md b/src/Symfony/Component/Process/CHANGELOG.md index 31b9ee6a25ba4..78241ab24d8e4 100644 --- a/src/Symfony/Component/Process/CHANGELOG.md +++ b/src/Symfony/Component/Process/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.4 +--- + + * Add `RunProcessMessage` and `RunProcessMessageHandler` + 5.2.0 ----- diff --git a/src/Symfony/Component/Process/Exception/RunProcessFailedException.php b/src/Symfony/Component/Process/Exception/RunProcessFailedException.php new file mode 100644 index 0000000000000..e7219d351ed46 --- /dev/null +++ b/src/Symfony/Component/Process/Exception/RunProcessFailedException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +use Symfony\Component\Process\Messenger\RunProcessContext; + +/** + * @author Kevin Bond + */ +final class RunProcessFailedException extends RuntimeException +{ + public function __construct(ProcessFailedException $exception, public readonly RunProcessContext $context) + { + parent::__construct($exception->getMessage(), $exception->getCode()); + } +} diff --git a/src/Symfony/Component/Process/Messenger/RunProcessContext.php b/src/Symfony/Component/Process/Messenger/RunProcessContext.php new file mode 100644 index 0000000000000..3c7da369397c1 --- /dev/null +++ b/src/Symfony/Component/Process/Messenger/RunProcessContext.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Messenger; + +use Symfony\Component\Process\Process; + +/** + * @author Kevin Bond + */ +final class RunProcessContext extends RunProcessMessage +{ + public readonly ?int $exitCode; + public readonly ?string $output; + public readonly ?string $errorOutput; + + public function __construct(RunProcessMessage $message, Process $process) + { + parent::__construct($message->command, $message->cwd, $message->env, $message->input, $message->timeout); + + $this->exitCode = $process->getExitCode(); + $this->output = $process->isOutputDisabled() ? null : $process->getOutput(); + $this->errorOutput = $process->isOutputDisabled() ? null : $process->getErrorOutput(); + } +} diff --git a/src/Symfony/Component/Process/Messenger/RunProcessMessage.php b/src/Symfony/Component/Process/Messenger/RunProcessMessage.php new file mode 100644 index 0000000000000..1d87e9c4369ba --- /dev/null +++ b/src/Symfony/Component/Process/Messenger/RunProcessMessage.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Messenger; + +/** + * @author Kevin Bond + */ +class RunProcessMessage implements \Stringable +{ + public function __construct( + public readonly array $command, + public readonly ?string $cwd = null, + public readonly ?array $env = null, + public readonly mixed $input = null, + public readonly ?float $timeout = 60.0, + ) { + } + + public function __toString(): string + { + return \implode(' ', $this->command); + } +} diff --git a/src/Symfony/Component/Process/Messenger/RunProcessMessageHandler.php b/src/Symfony/Component/Process/Messenger/RunProcessMessageHandler.php new file mode 100644 index 0000000000000..41c1934cc07f8 --- /dev/null +++ b/src/Symfony/Component/Process/Messenger/RunProcessMessageHandler.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Messenger; + +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Exception\RunProcessFailedException; +use Symfony\Component\Process\Process; + +/** + * @author Kevin Bond + */ +final class RunProcessMessageHandler +{ + public function __invoke(RunProcessMessage $message): RunProcessContext + { + $process = new Process($message->command, $message->cwd, $message->env, $message->input, $message->timeout); + + try { + return new RunProcessContext($message, $process->mustRun()); + } catch (ProcessFailedException $e) { + throw new RunProcessFailedException($e, new RunProcessContext($message, $e->getProcess())); + } + } +} diff --git a/src/Symfony/Component/Process/Tests/Messenger/RunProcessMessageHandlerTest.php b/src/Symfony/Component/Process/Tests/Messenger/RunProcessMessageHandlerTest.php new file mode 100644 index 0000000000000..10ed9bb2014a6 --- /dev/null +++ b/src/Symfony/Component/Process/Tests/Messenger/RunProcessMessageHandlerTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests\Messenger; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Process\Exception\RunProcessFailedException; +use Symfony\Component\Process\Messenger\RunProcessMessage; +use Symfony\Component\Process\Messenger\RunProcessMessageHandler; + +class RunProcessMessageHandlerTest extends TestCase +{ + public function testRunSuccessfulProcess() + { + $context = (new RunProcessMessageHandler())(new RunProcessMessage(['ls'], cwd: __DIR__)); + + $this->assertSame(['ls'], $context->command); + $this->assertSame(0, $context->exitCode); + $this->assertStringContainsString(basename(__FILE__), $context->output); + } + + public function testRunFailedProcess() + { + try { + (new RunProcessMessageHandler())(new RunProcessMessage(['invalid'])); + } catch (RunProcessFailedException $e) { + $this->assertSame(['invalid'], $e->context->command); + $this->assertSame(127, $e->context->exitCode); + + return; + } + + $this->fail('Exception not thrown'); + } +}